Skip to content

Commit f2cd4b0

Browse files
committed
Fix use-after-free serializing an array grown by an element's hook
The IS_ARRAY case of php_var_serialize_intern() walked the array's HashTable without holding a reference across php_var_serialize_nested_data(), which recurses into user hooks (__serialize, __sleep, Serializable::serialize). A hook that grows the same array through a by-reference alias reallocs the backing store mid-walk, so the iterator reads freed memory. Hold a ref across the walk, as the object path and var_dump/var_export already do, so the append separates a copy instead of reallocating in place.
1 parent 1feb201 commit f2cd4b0

2 files changed

Lines changed: 31 additions & 2 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
serialize(): a by-reference __serialize() that grows the array being walked must not free it
3+
--FILE--
4+
<?php
5+
class G {
6+
public $ref;
7+
public function __serialize(): array {
8+
for ($i = 0; $i < 128; $i++) {
9+
$this->ref[] = 'x' . $i;
10+
}
11+
return ['d' => 1];
12+
}
13+
}
14+
$g = new G();
15+
$inner = [$g, 'tail'];
16+
$g->ref = &$inner;
17+
$top = [&$inner];
18+
var_dump(serialize($top));
19+
?>
20+
--EXPECT--
21+
string(59) "a:1:{i:0;a:2:{i:0;O:1:"G":1:{s:1:"d";i:1;}i:1;s:4:"tail";}}"

ext/standard/var.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,13 +1301,21 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_
13011301
zend_release_properties(myht);
13021302
return;
13031303
}
1304-
case IS_ARRAY:
1304+
case IS_ARRAY: {
13051305
smart_str_appendl(buf, "a:", 2);
13061306
myht = Z_ARRVAL_P(struc);
1307+
bool rcn = !is_root && (in_rcn_array || GC_REFCOUNT(myht) > 1);
1308+
if (!(GC_FLAGS(myht) & GC_IMMUTABLE)) {
1309+
GC_ADDREF(myht);
1310+
}
13071311
php_var_serialize_nested_data(
13081312
buf, struc, myht, zend_array_count(myht), /* incomplete_class */ 0, var_hash,
1309-
!is_root && (in_rcn_array || GC_REFCOUNT(myht) > 1));
1313+
rcn);
1314+
if (!(GC_FLAGS(myht) & GC_IMMUTABLE)) {
1315+
GC_DTOR_NO_REF(myht);
1316+
}
13101317
return;
1318+
}
13111319
case IS_REFERENCE:
13121320
struc = Z_REFVAL_P(struc);
13131321
goto again;

0 commit comments

Comments
 (0)