mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
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.
This commit is contained in:
parent
b7a55aaff2
commit
50c87e92fc
2 changed files with 68 additions and 19 deletions
44
Zend/tests/gc_043.phpt
Normal file
44
Zend/tests/gc_043.phpt
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
--TEST--
|
||||||
|
GC buffer shouldn't get reused when removing nested data
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
$s = <<<'STR'
|
||||||
|
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:{}}}}
|
||||||
|
STR;
|
||||||
|
var_dump(unserialize($s));
|
||||||
|
gc_collect_cycles();
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
object(stdClass)#1 (2) {
|
||||||
|
["5"]=>
|
||||||
|
object(SplStack)#2 (2) {
|
||||||
|
["flags":"SplDoublyLinkedList":private]=>
|
||||||
|
int(4)
|
||||||
|
["dllist":"SplDoublyLinkedList":private]=>
|
||||||
|
array(2) {
|
||||||
|
[0]=>
|
||||||
|
*RECURSION*
|
||||||
|
[1]=>
|
||||||
|
object(stdClass)#3 (0) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
["0"]=>
|
||||||
|
object(RegexIterator)#4 (2) {
|
||||||
|
["replacement"]=>
|
||||||
|
NULL
|
||||||
|
["5"]=>
|
||||||
|
object(SplStack)#5 (2) {
|
||||||
|
["flags":"SplDoublyLinkedList":private]=>
|
||||||
|
int(4)
|
||||||
|
["dllist":"SplDoublyLinkedList":private]=>
|
||||||
|
array(2) {
|
||||||
|
[0]=>
|
||||||
|
*RECURSION*
|
||||||
|
[1]=>
|
||||||
|
object(stdClass)#6 (0) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1328,14 +1328,14 @@ static int gc_collect_roots(uint32_t *flags, gc_stack *stack)
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffer *root)
|
static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffer *root, gc_stack *stack)
|
||||||
{
|
{
|
||||||
HashTable *ht = NULL;
|
HashTable *ht = NULL;
|
||||||
Bucket *p, *end;
|
Bucket *p, *end;
|
||||||
zval *zv;
|
zval *zv;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
GC_STACK_DCL(stack);
|
||||||
|
|
||||||
tail_call:
|
|
||||||
do {
|
do {
|
||||||
if (root) {
|
if (root) {
|
||||||
root = NULL;
|
root = NULL;
|
||||||
|
@ -1348,11 +1348,11 @@ tail_call:
|
||||||
} else if (GC_TYPE(ref) == IS_REFERENCE) {
|
} else if (GC_TYPE(ref) == IS_REFERENCE) {
|
||||||
if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
|
if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
|
||||||
ref = Z_COUNTED(((zend_reference*)ref)->val);
|
ref = Z_COUNTED(((zend_reference*)ref)->val);
|
||||||
goto tail_call;
|
continue;
|
||||||
}
|
}
|
||||||
return count;
|
goto next;
|
||||||
} else {
|
} else {
|
||||||
return count;
|
goto next;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GC_TYPE(ref) == IS_OBJECT) {
|
if (GC_TYPE(ref) == IS_OBJECT) {
|
||||||
|
@ -1364,10 +1364,10 @@ tail_call:
|
||||||
|
|
||||||
ht = obj->handlers->get_gc(obj, &zv, &n);
|
ht = obj->handlers->get_gc(obj, &zv, &n);
|
||||||
if (EXPECTED(!ht)) {
|
if (EXPECTED(!ht)) {
|
||||||
if (!n) return count;
|
if (!n) goto next;
|
||||||
end = zv + n;
|
end = zv + n;
|
||||||
while (!Z_REFCOUNTED_P(--end)) {
|
while (!Z_REFCOUNTED_P(--end)) {
|
||||||
if (zv == end) return count;
|
if (zv == end) goto next;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!n) goto handle_ht;
|
if (!n) goto handle_ht;
|
||||||
|
@ -1376,13 +1376,13 @@ tail_call:
|
||||||
while (zv != end) {
|
while (zv != end) {
|
||||||
if (Z_REFCOUNTED_P(zv)) {
|
if (Z_REFCOUNTED_P(zv)) {
|
||||||
ref = Z_COUNTED_P(zv);
|
ref = Z_COUNTED_P(zv);
|
||||||
count += gc_remove_nested_data_from_buffer(ref, NULL);
|
GC_STACK_PUSH(ref);
|
||||||
}
|
}
|
||||||
zv++;
|
zv++;
|
||||||
}
|
}
|
||||||
if (EXPECTED(!ht)) {
|
if (EXPECTED(!ht)) {
|
||||||
ref = Z_COUNTED_P(zv);
|
ref = Z_COUNTED_P(zv);
|
||||||
goto tail_call;
|
continue;
|
||||||
}
|
}
|
||||||
handle_ht:
|
handle_ht:
|
||||||
if (GC_REF_ADDRESS(ht) != 0 && GC_REF_CHECK_COLOR(ht, GC_BLACK)) {
|
if (GC_REF_ADDRESS(ht) != 0 && GC_REF_CHECK_COLOR(ht, GC_BLACK)) {
|
||||||
|
@ -1390,15 +1390,15 @@ handle_ht:
|
||||||
GC_REMOVE_FROM_BUFFER(ht);
|
GC_REMOVE_FROM_BUFFER(ht);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return count;
|
goto next;
|
||||||
}
|
}
|
||||||
} else if (GC_TYPE(ref) == IS_ARRAY) {
|
} else if (GC_TYPE(ref) == IS_ARRAY) {
|
||||||
ht = (zend_array*)ref;
|
ht = (zend_array*)ref;
|
||||||
} else {
|
} else {
|
||||||
return count;
|
goto next;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ht->nNumUsed) return count;
|
if (!ht->nNumUsed) goto next;
|
||||||
p = ht->arData;
|
p = ht->arData;
|
||||||
end = p + ht->nNumUsed;
|
end = p + ht->nNumUsed;
|
||||||
while (1) {
|
while (1) {
|
||||||
|
@ -1410,7 +1410,7 @@ handle_ht:
|
||||||
if (Z_REFCOUNTED_P(zv)) {
|
if (Z_REFCOUNTED_P(zv)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (p == end) return count;
|
if (p == end) goto next;
|
||||||
}
|
}
|
||||||
while (p != end) {
|
while (p != end) {
|
||||||
zv = &p->val;
|
zv = &p->val;
|
||||||
|
@ -1419,7 +1419,7 @@ handle_ht:
|
||||||
}
|
}
|
||||||
if (Z_REFCOUNTED_P(zv)) {
|
if (Z_REFCOUNTED_P(zv)) {
|
||||||
ref = Z_COUNTED_P(zv);
|
ref = Z_COUNTED_P(zv);
|
||||||
count += gc_remove_nested_data_from_buffer(ref, NULL);
|
GC_STACK_PUSH(ref);
|
||||||
}
|
}
|
||||||
p++;
|
p++;
|
||||||
}
|
}
|
||||||
|
@ -1428,8 +1428,12 @@ handle_ht:
|
||||||
zv = Z_INDIRECT_P(zv);
|
zv = Z_INDIRECT_P(zv);
|
||||||
}
|
}
|
||||||
ref = Z_COUNTED_P(zv);
|
ref = Z_COUNTED_P(zv);
|
||||||
goto tail_call;
|
continue;
|
||||||
} while (0);
|
|
||||||
|
next:
|
||||||
|
ref = GC_STACK_POP();
|
||||||
|
} while (ref);
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void zend_get_gc_buffer_release();
|
static void zend_get_gc_buffer_release();
|
||||||
|
@ -1464,11 +1468,10 @@ ZEND_API int zend_gc_collect_cycles(void)
|
||||||
GC_TRACE("Collecting roots");
|
GC_TRACE("Collecting roots");
|
||||||
count = gc_collect_roots(&gc_flags, &stack);
|
count = gc_collect_roots(&gc_flags, &stack);
|
||||||
|
|
||||||
gc_stack_free(&stack);
|
|
||||||
|
|
||||||
if (!GC_G(num_roots)) {
|
if (!GC_G(num_roots)) {
|
||||||
/* nothing to free */
|
/* nothing to free */
|
||||||
GC_TRACE("Nothing to free");
|
GC_TRACE("Nothing to free");
|
||||||
|
gc_stack_free(&stack);
|
||||||
zend_get_gc_buffer_release();
|
zend_get_gc_buffer_release();
|
||||||
GC_G(gc_active) = 0;
|
GC_G(gc_active) = 0;
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1518,7 +1521,7 @@ ZEND_API int zend_gc_collect_cycles(void)
|
||||||
while (idx != end) {
|
while (idx != end) {
|
||||||
if (GC_IS_DTOR_GARBAGE(current->ref)) {
|
if (GC_IS_DTOR_GARBAGE(current->ref)) {
|
||||||
p = GC_GET_PTR(current->ref);
|
p = GC_GET_PTR(current->ref);
|
||||||
count -= gc_remove_nested_data_from_buffer(p, current);
|
count -= gc_remove_nested_data_from_buffer(p, current, &stack);
|
||||||
}
|
}
|
||||||
current++;
|
current++;
|
||||||
idx++;
|
idx++;
|
||||||
|
@ -1557,6 +1560,8 @@ ZEND_API int zend_gc_collect_cycles(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gc_stack_free(&stack);
|
||||||
|
|
||||||
/* Destroy zvals. The root buffer may be reallocated. */
|
/* Destroy zvals. The root buffer may be reallocated. */
|
||||||
GC_TRACE("Destroying zvals");
|
GC_TRACE("Destroying zvals");
|
||||||
idx = GC_FIRST_ROOT;
|
idx = GC_FIRST_ROOT;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue