Skip to content

Commit d55c30e

Browse files
committed
Fixes infinite recursion introduced by patch to SplFixedArray
Closes GH-8079 Under most circumstances, the zend_hash_index_update is setting the value to itself when the hash table's reference count is already 2
1 parent 6fbf5a6 commit d55c30e

File tree

2 files changed

+93
-0
lines changed

2 files changed

+93
-0
lines changed

ext/spl/spl_fixedarray.c

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,55 @@ static HashTable* spl_fixedarray_object_get_gc(zend_object *obj, zval **table, i
199199
return ht;
200200
}
201201

202+
static zend_bool spl_fixedarray_shallow_identical(zval *v1, zval *v2)
203+
{
204+
if (Z_TYPE_P(v1) != Z_TYPE_P(v2)) {
205+
return false;
206+
}
207+
switch (Z_TYPE_P(v1)) {
208+
case IS_NULL:
209+
case IS_FALSE:
210+
case IS_TRUE:
211+
return true;
212+
case IS_LONG:
213+
return Z_LVAL_P(v1) == Z_LVAL_P(v2);
214+
case IS_DOUBLE: {
215+
double d1 = Z_DVAL_P(v1);
216+
double d2 = Z_DVAL_P(v2);
217+
/* Also check for NAN */
218+
return d1 == d2 || (d1 != d1 && d2 != d2);
219+
}
220+
case IS_STRING:
221+
return Z_STR_P(v1) == Z_STR_P(v2); /* Same pointer */
222+
case IS_ARRAY:
223+
return Z_ARR_P(v1) == Z_ARR_P(v2); /* Same pointer */
224+
case IS_OBJECT:
225+
return Z_OBJ_P(v1) == Z_OBJ_P(v2);
226+
case IS_RESOURCE:
227+
return Z_RES_P(v1) == Z_RES_P(v2);
228+
default:
229+
return false;
230+
}
231+
}
232+
233+
static zend_bool spl_fixedarray_object_needs_rebuild(spl_fixedarray_object *intern, HashTable *ht, zend_long j)
234+
{
235+
for (zend_long i = 0; i < intern->array.size; i++) {
236+
zval *old = zend_hash_index_find(ht, i);
237+
if (old == NULL || !spl_fixedarray_shallow_identical(&intern->array.elements[i], old)) {
238+
return true;
239+
}
240+
}
241+
if (j > intern->array.size) {
242+
for (zend_long i = intern->array.size; i < j; ++i) {
243+
if (zend_hash_index_exists(ht, i)) {
244+
return true;
245+
}
246+
}
247+
}
248+
return false;
249+
}
250+
202251
static HashTable* spl_fixedarray_object_get_properties(zend_object *obj)
203252
{
204253
spl_fixedarray_object *intern = spl_fixed_array_from_obj(obj);
@@ -208,6 +257,19 @@ static HashTable* spl_fixedarray_object_get_properties(zend_object *obj)
208257
zend_long j = zend_hash_num_elements(ht);
209258

210259
if (GC_REFCOUNT(ht) > 1) {
260+
/*
261+
* Usually, the reference count of the hash table is 1,
262+
* except during cyclic reference cycles.
263+
*
264+
* Attempt to maintain the DEBUG invariant that a hash table isn't modified during iteration.
265+
*
266+
* See https://github.com/php/php-src/issues/8079 and ext/spl/tests/fixedarray_022.phpt
267+
* Also see https://github.com/php/php-src/issues/8044 for alternate considered approaches.
268+
*/
269+
if (!spl_fixedarray_object_needs_rebuild(intern, ht, j)) {
270+
/* Return the same hash table so that recursion cycle detection works in internal functions. */
271+
return ht;
272+
}
211273
intern->std.properties = zend_array_dup(ht);
212274
if (!(GC_FLAGS(ht) & GC_IMMUTABLE)) {
213275
GC_DELREF(ht);

ext/spl/tests/fixedarray_023.phpt

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

0 commit comments

Comments
 (0)