php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #78549 Stack overflow due to nested serialized input
Submitted: 2019-09-16 12:32 UTC Modified: 2019-09-30 08:32 UTC
From: marc dot schoenefeld at gmx dot org Assigned: nikic (profile)
Status: Closed Package: Reproducible crash
PHP Version: 7.3.9 OS: CentOS 7 / Generic
Private report: No CVE-ID: None
 [2019-09-16 12:32 UTC] marc dot schoenefeld at gmx dot org
Description:
------------
php 7.3.9 (tested with locally compiled build on CentOS 7) can be DoSed with serialized data, causing a stack overflow scenario [see below]. 
 


Test script:
---------------
<?php
$filehandle = fopen($argv[1], "r") or die("Unable to open file!");
$data = fread($filehandle,filesize($argv[1]));
fclose($filehandle);

$string = unserialize($data);
?>

Expected result:
----------------
No crash

Actual result:
--------------
php-7.3.9/sapi/cli/php sertest.php php_7_3_9_stack_overflow.ser 

 

Program received signal SIGSEGV, Segmentation fault.

php_var_unserialize_internal (rval=rval@entry=0x7fb2a70dd240, p=p@entry=0x7ffe3f34b120, max=max@entry=0x7fb2a833ad95 "", 

    var_hash=var_hash@entry=0x7ffe3f34b128, as_key=as_key@entry=0)

    at /php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:1345

1345 zend_long elements = parse_iv(start + 2);

Missing separate debuginfos, use: debuginfo-install libxml2-2.9.1-6.el7_2.3.x86_64 xz-libs-5.2.2-1.el7.x86_64

(gdb) bt

#0  php_var_unserialize_internal (rval=rval@entry=0x7fb2a70dd240, p=p@entry=0x7ffe3f34b120, max=max@entry=0x7fb2a833ad95 "", 

    var_hash=var_hash@entry=0x7ffe3f34b128, as_key=as_key@entry=0)

    at php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:1345

#1  0x00000000006ca221 in process_nested_data (objprops=0, elements=0, ht=0x7fb2a70df268, var_hash=0x7ffe3f34b128, max=0x7fb2a833ad95 "", 

    p=0x7ffe3f34b120, rval=0x7fb2a70dd240) at /php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:502

#2  php_var_unserialize_internal (rval=rval@entry=0x7fb2a70dd100, p=p@entry=0x7ffe3f34b120, max=max@entry=0x7fb2a833ad95 "", 

    var_hash=var_hash@entry=0x7ffe3f34b128, as_key=as_key@entry=0)

    at /php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:1370

#3  0x00000000006ca221 in process_nested_data (objprops=0, elements=0, ht=0x7fb2a70df230, var_hash=0x7ffe3f34b128, max=0x7fb2a833ad95 "", 

    p=0x7ffe3f34b120, rval=0x7fb2a70dd100) at /php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:502

#4  php_var_unserialize_internal (rval=rval@entry=0x7fb2a70dcfc0, p=p@entry=0x7ffe3f34b120, max=max@entry=0x7fb2a833ad95 "", 

    var_hash=var_hash@entry=0x7ffe3f34b128, as_key=as_key@entry=0)

    at /php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:1370

#5  0x00000000006ca221 in process_nested_data (objprops=0, elements=0, ht=0x7fb2a70df1f8, var_hash=0x7ffe3f34b128, max=0x7fb2a833ad95 "", 

    p=0x7ffe3f34b120, rval=0x7fb2a70dcfc0) at /php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:502

#6  php_var_unserialize_internal (rval=rval@entry=0x7fb2a70dce80, p=p@entry=0x7ffe3f34b120, max=max@entry=0x7fb2a833ad95 "", 

    var_hash=var_hash@entry=0x7ffe3f34b128, as_key=as_key@entry=0)

    at /php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:1370

#7  0x00000000006ca221 in process_nested_data (objprops=0, elements=0, ht=0x7fb2a70df1c0, var_hash=0x7ffe3f34b128, max=0x7fb2a833ad95 "", 

    p=0x7ffe3f34b120, rval=0x7fb2a70dce80) at /php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:502

#8  php_var_unserialize_internal (rval=rval@entry=0x7fb2a70dcd40, p=p@entry=0x7ffe3f34b120, max=max@entry=0x7fb2a833ad95 "", 

    var_hash=var_hash@entry=0x7ffe3f34b128, as_key=as_key@entry=0)

    at /php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:1370

#9  0x00000000006ca221 in process_nested_data (objprops=0, elements=0, ht=0x7fb2a70df188, var_hash=0x7ffe3f34b128, max=0x7fb2a833ad95 "", 

    p=0x7ffe3f34b120, rval=0x7fb2a70dcd40) at /php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:502

#10 php_var_unserialize_internal (rval=rval@entry=0x7fb2a70dcc00, p=p@entry=0x7ffe3f34b120, max=max@entry=0x7fb2a833ad95 "", 

    var_hash=var_hash@entry=0x7ffe3f34b128, as_key=as_key@entry=0)

    at /php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:1370

#11 0x00000000006ca221 in process_nested_data (objprops=0, elements=0, ht=0x7fb2a70df150, var_hash=0x7ffe3f34b128, max=0x7fb2a833ad95 "", 

    p=0x7ffe3f34b120, rval=0x7fb2a70dcc00) at /php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:502

#12 php_var_unserialize_internal (rval=rval@entry=0x7fb2a70dcac0, p=p@entry=0x7ffe3f34b120, max=max@entry=0x7fb2a833ad95 "", 

    var_hash=var_hash@entry=0x7ffe3f34b128, as_key=as_key@entry=0)

    at /php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:1370

#13 0x00000000006ca221 in process_nested_data (objprops=0, elements=0, ht=0x7fb2a70df118, var_hash=0x7ffe3f34b128, max=0x7fb2a833ad95 "", 

    p=0x7ffe3f34b120, rval=0x7fb2a70dcac0) at /php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:502

#14 php_var_unserialize_internal (rval=rval@entry=0x7fb2a70dc980, p=p@entry=0x7ffe3f34b120, max=max@entry=0x7fb2a833ad95 "", 

    var_hash=var_hash@entry=0x7ffe3f34b128, as_key=as_key@entry=0)

    at /php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:1370

#15 0x00000000006ca221 in process_nested_data (objprops=0, elements=0, ht=0x7fb2a70df0e0, var_hash=0x7ffe3f34b128, max=0x7fb2a833ad95 "", 

    p=0x7ffe3f34b120, rval=0x7fb2a70dc980) at /php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:502

#16 php_var_unserialize_internal (rval=rval@entry=0x7fb2a70dc840, p=p@entry=0x7ffe3f34b120, max=max@entry=0x7fb2a833ad95 "", 

    var_hash=var_hash@entry=0x7ffe3f34b128, as_key=as_key@entry=0)

    at /php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:1370

#17 0x00000000006ca221 in process_nested_data (objprops=0, elements=0, ht=0x7fb2a70df0a8, var_hash=0x7ffe3f34b128, max=0x7fb2a833ad95 "", 

    p=0x7ffe3f34b120, rval=0x7fb2a70dc840) at /php/linux_build/php-7.3.9/ext/standard/var_unserializer.c:502

---Type <return> to continue, or q <return> to quit---  q

Quit

(gdb) disass $pc-12,$pc+10

Dump of assembler code from 0x6ca14e to 0x6ca164:

   0x00000000006ca14e <php_var_unserialize_internal+2430>: jne    0x6c9928 <php_var_unserialize_internal+344>

   0x00000000006ca154 <php_var_unserialize_internal+2436>: xor    esi,esi

   0x00000000006ca156 <php_var_unserialize_internal+2438>: add    r12,0x3

=> 0x00000000006ca15a <php_var_unserialize_internal+2442>: call   0x42ca21 <parse_iv2>

   0x00000000006ca15f <php_var_unserialize_internal+2447>: test   rbx,rbx

   0x00000000006ca162 <php_var_unserialize_internal+2450>: mov    r13,rax

End of assembler dump.

Patches

php_7_3_9_stack_overflow.ser.zip.b64.patch (last revision 2019-09-16 13:27 UTC by marc dot schoenefeld at gmx dot org)

Add a Patch

Pull Requests

Pull requests:

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2019-09-16 12:52 UTC] nikic@php.net
What are the contents of php_7_3_9_stack_overflow.ser? I can imagine, but if you already have the test case...
 [2019-09-16 13:27 UTC] marc dot schoenefeld at gmx dot org
The following patch has been added/updated:

Patch Name: php_7_3_9_stack_overflow.ser.zip.b64.patch
Revision:   1568640455
URL:        https://bugs.php.net/patch-display.php?bug=78549&patch=php_7_3_9_stack_overflow.ser.zip.b64.patch&revision=1568640455
 [2019-09-16 13:51 UTC] nikic@php.net
https://gist.githubusercontent.com/nikic/200240ca7830c6e5d587b3b4aad927eb/raw/d8b02e0bb1deca31025dbaf50a46ab4ab792bf68/php_7_3_9_stack_overflow.ser

I doubt we're going to rewriting the unserializer in a non-recursive fashion, so we can either introduce an arbitrary depth limit, or just ignore this.
 [2019-09-23 11:38 UTC] nikic@php.net
-Status: Open +Status: Verified
 [2019-09-23 11:38 UTC] nikic@php.net
Fuzzing seems to hit this quite often, so we should probably do something about it.

Script to find the limit:

<?php
$str = 'i:0;';
for ($i = 0; $i < 10000; $i++) {
    $str = 'a:1:{i:0;' . $str . '}';
    if ($i % 10 == 0) {
        echo "$i\n";
        unserialize($str);
    }
}
var_dump($str);

I get 5620 on a debug build and 7690 on a release/assert build.

Trying the same with O:8:"stdClass":1 I get 4630 and 7690.

We can add an unserialize option for the max_depth and default it to something like 4000.
 [2019-09-24 10:18 UTC] nikic@php.net
The following pull request has been associated:

Patch Name: Add max_depth option to unserialize()
On GitHub:  https://github.com/php/php-src/pull/4742
Patch:      https://github.com/php/php-src/pull/4742.patch
 [2019-09-30 08:32 UTC] nikic@php.net
-Status: Verified +Status: Closed -Assigned To: +Assigned To: nikic
 
PHP Copyright © 2001-2019 The PHP Group
All rights reserved.
Last updated: Mon Dec 09 00:01:25 2019 UTC