Skip to content

Commit cc8abaf

Browse files
authored
Fix use-after-free serializing an array grown by an element's hook (php#22714)
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 00dc391 commit cc8abaf

2 files changed

Lines changed: 27 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: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,13 +1303,17 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_
13031303
zend_release_properties(myht);
13041304
return;
13051305
}
1306-
case IS_ARRAY:
1306+
case IS_ARRAY: {
13071307
smart_str_appendl(buf, "a:", 2);
13081308
myht = Z_ARRVAL_P(struc);
1309+
bool rcn = !is_root && (in_rcn_array || GC_REFCOUNT(myht) > 1);
1310+
GC_TRY_ADDREF(myht);
13091311
php_var_serialize_nested_data(
13101312
buf, struc, myht, zend_array_count(myht), /* incomplete_class */ false, var_hash,
1311-
!is_root && (in_rcn_array || GC_REFCOUNT(myht) > 1));
1313+
rcn);
1314+
GC_TRY_DTOR_NO_REF(myht);
13121315
return;
1316+
}
13131317
case IS_REFERENCE:
13141318
struc = Z_REFVAL_P(struc);
13151319
goto again;

0 commit comments

Comments
 (0)