Skip to content

Commit 6e736d1

Browse files
committed
Fixes infinite recursion introduced by patch to SplFixedArray
Closes GH-8079 Track whether the spl_fixedarray was modified since the last call to get_properties
1 parent bfe9531 commit 6e736d1

File tree

2 files changed

+61
-0
lines changed

2 files changed

+61
-0
lines changed

ext/spl/spl_fixedarray.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ ZEND_GET_MODULE(spl_fixedarray)
4242
typedef struct _spl_fixedarray {
4343
zend_long size;
4444
zval *elements;
45+
/* True if this was modified after the last call to get_properties or the hash table wasn't rebuilt. */
46+
bool should_rebuild_properties;
4547
} spl_fixedarray;
4648

4749
typedef struct _spl_fixedarray_object {
@@ -104,6 +106,7 @@ static void spl_fixedarray_init(spl_fixedarray *array, zend_long size)
104106
array->size = 0; /* reset size in case ecalloc() fails */
105107
array->elements = safe_emalloc(size, sizeof(zval), 0);
106108
array->size = size;
109+
array->should_rebuild_properties = true;
107110
spl_fixedarray_init_elems(array, 0, size);
108111
} else {
109112
spl_fixedarray_default_ctor(array);
@@ -166,6 +169,7 @@ static void spl_fixedarray_resize(spl_fixedarray *array, zend_long size)
166169
/* nothing to do */
167170
return;
168171
}
172+
array->should_rebuild_properties = true;
169173

170174
/* first initialization */
171175
if (array->size == 0) {
@@ -205,6 +209,22 @@ static HashTable* spl_fixedarray_object_get_properties(zend_object *obj)
205209
HashTable *ht = zend_std_get_properties(obj);
206210

207211
if (!spl_fixedarray_empty(&intern->array)) {
212+
/*
213+
* Usually, the reference count of the hash table is 1,
214+
* except during cyclic reference cycles.
215+
*
216+
* Maintain the DEBUG invariant that a hash table isn't modified during iteration,
217+
* and avoid unnecessary work rebuilding a hash table for unmodified properties.
218+
*
219+
* See https://github.com/php/php-src/issues/8079 and ext/spl/tests/fixedarray_022.phpt
220+
* Also see https://github.com/php/php-src/issues/8044 for alternate considered approaches.
221+
*/
222+
if (!intern->array.should_rebuild_properties) {
223+
/* Return the same hash table so that recursion cycle detection works in internal functions. */
224+
return ht;
225+
}
226+
intern->array.should_rebuild_properties = false;
227+
208228
zend_long j = zend_hash_num_elements(ht);
209229

210230
if (GC_REFCOUNT(ht) > 1) {
@@ -354,6 +374,9 @@ static zval *spl_fixedarray_object_read_dimension(zend_object *object, zval *off
354374
}
355375
return &EG(uninitialized_zval);
356376
}
377+
if (type != BP_VAR_IS && type != BP_VAR_R) {
378+
intern->array.should_rebuild_properties = true;
379+
}
357380

358381
return spl_fixedarray_object_read_dimension_helper(intern, offset);
359382
}
@@ -378,6 +401,7 @@ static void spl_fixedarray_object_write_dimension_helper(spl_fixedarray_object *
378401
zend_throw_exception(spl_ce_RuntimeException, "Index invalid or out of range", 0);
379402
return;
380403
} else {
404+
intern->array.should_rebuild_properties = true;
381405
/* Fix #81429 */
382406
zval *ptr = &(intern->array.elements[index]);
383407
zval tmp;
@@ -425,6 +449,7 @@ static void spl_fixedarray_object_unset_dimension_helper(spl_fixedarray_object *
425449
zend_throw_exception(spl_ce_RuntimeException, "Index invalid or out of range", 0);
426450
return;
427451
} else {
452+
intern->array.should_rebuild_properties = true;
428453
zval_ptr_dtor(&(intern->array.elements[index]));
429454
ZVAL_NULL(&intern->array.elements[index]);
430455
}

ext/spl/tests/fixedarray_023.phpt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--TEST--
2+
SPL: FixedArray: Infinite loop in var_export bugfix
3+
--FILE--
4+
<?php
5+
call_user_func(function () {
6+
$x = new SplFixedArray(4);
7+
$x[0] = NAN; // Test NAN just in case this check is incorrectly refactored to use zend_is_identical
8+
$x[1] = 0.0;
9+
$x[2] = $x;
10+
$x[3] = $x;
11+
var_export($x);
12+
echo "\n";
13+
$x[1] = -0.0;
14+
debug_zval_dump($x);
15+
});
16+
?>
17+
--EXPECTF--
18+
Warning: var_export does not handle circular references in %s on line 8
19+
20+
Warning: var_export does not handle circular references in %s on line 8
21+
SplFixedArray::__set_state(array(
22+
0 => NAN,
23+
1 => 0.0,
24+
2 => NULL,
25+
3 => NULL,
26+
))
27+
object(SplFixedArray)#2 (4) refcount(6){
28+
[0]=>
29+
float(NAN)
30+
[1]=>
31+
float(-0)
32+
[2]=>
33+
*RECURSION*
34+
[3]=>
35+
*RECURSION*
36+
}

0 commit comments

Comments
 (0)