Skip to content

Commit 50c87e9

Browse files
committed
Use GC stack in nested data removal
We should be doing this anyway to prevent stack overflow, but on master this is important for an additional reason: The temporary GC buffer provided for get_gc handlers may get reused if the scan is performed recursively instead of indirected via the GC stack. This fixes oss-fuzz #23350.
1 parent b7a55aa commit 50c87e9

File tree

2 files changed

+68
-19
lines changed

2 files changed

+68
-19
lines changed

Zend/tests/gc_043.phpt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
GC buffer shouldn't get reused when removing nested data
3+
--FILE--
4+
<?php
5+
$s = <<<'STR'
6+
O:8:"stdClass":2:{i:5;C:8:"SplStack":29:{i:4;:r:1;:O:8:"stdClass":0:{}}i:0;O:13:"RegexIterator":1:{i:5;C:8:"SplStack":29:{i:4;:r:1;:O:8:"stdClass":0:{}}}}
7+
STR;
8+
var_dump(unserialize($s));
9+
gc_collect_cycles();
10+
?>
11+
--EXPECT--
12+
object(stdClass)#1 (2) {
13+
["5"]=>
14+
object(SplStack)#2 (2) {
15+
["flags":"SplDoublyLinkedList":private]=>
16+
int(4)
17+
["dllist":"SplDoublyLinkedList":private]=>
18+
array(2) {
19+
[0]=>
20+
*RECURSION*
21+
[1]=>
22+
object(stdClass)#3 (0) {
23+
}
24+
}
25+
}
26+
["0"]=>
27+
object(RegexIterator)#4 (2) {
28+
["replacement"]=>
29+
NULL
30+
["5"]=>
31+
object(SplStack)#5 (2) {
32+
["flags":"SplDoublyLinkedList":private]=>
33+
int(4)
34+
["dllist":"SplDoublyLinkedList":private]=>
35+
array(2) {
36+
[0]=>
37+
*RECURSION*
38+
[1]=>
39+
object(stdClass)#6 (0) {
40+
}
41+
}
42+
}
43+
}
44+
}

Zend/zend_gc.c

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,14 +1328,14 @@ static int gc_collect_roots(uint32_t *flags, gc_stack *stack)
13281328
return count;
13291329
}
13301330

1331-
static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffer *root)
1331+
static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffer *root, gc_stack *stack)
13321332
{
13331333
HashTable *ht = NULL;
13341334
Bucket *p, *end;
13351335
zval *zv;
13361336
int count = 0;
1337+
GC_STACK_DCL(stack);
13371338

1338-
tail_call:
13391339
do {
13401340
if (root) {
13411341
root = NULL;
@@ -1348,11 +1348,11 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe
13481348
} else if (GC_TYPE(ref) == IS_REFERENCE) {
13491349
if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
13501350
ref = Z_COUNTED(((zend_reference*)ref)->val);
1351-
goto tail_call;
1351+
continue;
13521352
}
1353-
return count;
1353+
goto next;
13541354
} else {
1355-
return count;
1355+
goto next;
13561356
}
13571357

13581358
if (GC_TYPE(ref) == IS_OBJECT) {
@@ -1364,10 +1364,10 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe
13641364

13651365
ht = obj->handlers->get_gc(obj, &zv, &n);
13661366
if (EXPECTED(!ht)) {
1367-
if (!n) return count;
1367+
if (!n) goto next;
13681368
end = zv + n;
13691369
while (!Z_REFCOUNTED_P(--end)) {
1370-
if (zv == end) return count;
1370+
if (zv == end) goto next;
13711371
}
13721372
} else {
13731373
if (!n) goto handle_ht;
@@ -1376,29 +1376,29 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe
13761376
while (zv != end) {
13771377
if (Z_REFCOUNTED_P(zv)) {
13781378
ref = Z_COUNTED_P(zv);
1379-
count += gc_remove_nested_data_from_buffer(ref, NULL);
1379+
GC_STACK_PUSH(ref);
13801380
}
13811381
zv++;
13821382
}
13831383
if (EXPECTED(!ht)) {
13841384
ref = Z_COUNTED_P(zv);
1385-
goto tail_call;
1385+
continue;
13861386
}
13871387
handle_ht:
13881388
if (GC_REF_ADDRESS(ht) != 0 && GC_REF_CHECK_COLOR(ht, GC_BLACK)) {
13891389
GC_TRACE_REF(ht, "removing from buffer");
13901390
GC_REMOVE_FROM_BUFFER(ht);
13911391
}
13921392
} else {
1393-
return count;
1393+
goto next;
13941394
}
13951395
} else if (GC_TYPE(ref) == IS_ARRAY) {
13961396
ht = (zend_array*)ref;
13971397
} else {
1398-
return count;
1398+
goto next;
13991399
}
14001400

1401-
if (!ht->nNumUsed) return count;
1401+
if (!ht->nNumUsed) goto next;
14021402
p = ht->arData;
14031403
end = p + ht->nNumUsed;
14041404
while (1) {
@@ -1410,7 +1410,7 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe
14101410
if (Z_REFCOUNTED_P(zv)) {
14111411
break;
14121412
}
1413-
if (p == end) return count;
1413+
if (p == end) goto next;
14141414
}
14151415
while (p != end) {
14161416
zv = &p->val;
@@ -1419,7 +1419,7 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe
14191419
}
14201420
if (Z_REFCOUNTED_P(zv)) {
14211421
ref = Z_COUNTED_P(zv);
1422-
count += gc_remove_nested_data_from_buffer(ref, NULL);
1422+
GC_STACK_PUSH(ref);
14231423
}
14241424
p++;
14251425
}
@@ -1428,8 +1428,12 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe
14281428
zv = Z_INDIRECT_P(zv);
14291429
}
14301430
ref = Z_COUNTED_P(zv);
1431-
goto tail_call;
1432-
} while (0);
1431+
continue;
1432+
1433+
next:
1434+
ref = GC_STACK_POP();
1435+
} while (ref);
1436+
return count;
14331437
}
14341438

14351439
static void zend_get_gc_buffer_release();
@@ -1464,11 +1468,10 @@ ZEND_API int zend_gc_collect_cycles(void)
14641468
GC_TRACE("Collecting roots");
14651469
count = gc_collect_roots(&gc_flags, &stack);
14661470

1467-
gc_stack_free(&stack);
1468-
14691471
if (!GC_G(num_roots)) {
14701472
/* nothing to free */
14711473
GC_TRACE("Nothing to free");
1474+
gc_stack_free(&stack);
14721475
zend_get_gc_buffer_release();
14731476
GC_G(gc_active) = 0;
14741477
return 0;
@@ -1518,7 +1521,7 @@ ZEND_API int zend_gc_collect_cycles(void)
15181521
while (idx != end) {
15191522
if (GC_IS_DTOR_GARBAGE(current->ref)) {
15201523
p = GC_GET_PTR(current->ref);
1521-
count -= gc_remove_nested_data_from_buffer(p, current);
1524+
count -= gc_remove_nested_data_from_buffer(p, current, &stack);
15221525
}
15231526
current++;
15241527
idx++;
@@ -1557,6 +1560,8 @@ ZEND_API int zend_gc_collect_cycles(void)
15571560
}
15581561
}
15591562

1563+
gc_stack_free(&stack);
1564+
15601565
/* Destroy zvals. The root buffer may be reallocated. */
15611566
GC_TRACE("Destroying zvals");
15621567
idx = GC_FIRST_ROOT;

0 commit comments

Comments
 (0)