Skip to content

Commit 5721132

Browse files
committed
- Fixed bug #53071 (SPLObjectStorage defeats gc_collect_cycles).
1 parent 3cb6a46 commit 5721132

File tree

3 files changed

+90
-2
lines changed

3 files changed

+90
-2
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
large amount of data) (CVE-2010-3710). (Adam)
4242

4343
- Fixed bug #53144 (Segfault in SplObjectStorage::removeAll()). (Felipe)
44+
- Fixed bug #53071 (SPLObjectStorage defeats gc_collect_cycles). (Gustavo)
4445
- Fixed bug #53006 (stream_get_contents has an unpredictable behavior when the
4546
underlying stream does not support seeking). (Gustavo)
4647
- Fixed bug #53021 (In html_entity_decode, failure to convert numeric entities

ext/spl/spl_observer.c

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@ static HashTable* spl_object_storage_debug_info(zval *obj, int *is_temp TSRMLS_D
255255
*is_temp = 0;
256256

257257
props = Z_OBJPROP_P(obj);
258+
zend_hash_del(props, "\x00gcdata", sizeof("\x00gcdata"));
259+
258260
if (intern->debug_info == NULL) {
259261
ALLOC_HASHTABLE(intern->debug_info);
260262
ZEND_INIT_SYMTABLE_EX(intern->debug_info, zend_hash_num_elements(props) + 1, 0);
@@ -269,10 +271,11 @@ static HashTable* spl_object_storage_debug_info(zval *obj, int *is_temp TSRMLS_D
269271
zend_hash_internal_pointer_reset_ex(&intern->storage, &pos);
270272
while (zend_hash_get_current_data_ex(&intern->storage, (void **)&element, &pos) == SUCCESS) {
271273
php_spl_object_hash(element->obj, md5str TSRMLS_CC);
272-
Z_ADDREF_P(element->obj);
273-
Z_ADDREF_P(element->inf);
274274
MAKE_STD_ZVAL(tmp);
275275
array_init(tmp);
276+
/* Incrementing the refcount of obj and inf would confuse the garbage collector.
277+
* Prefer to null the destructor */
278+
Z_ARRVAL_P(tmp)->pDestructor = NULL;
276279
add_assoc_zval_ex(tmp, "obj", sizeof("obj"), element->obj);
277280
add_assoc_zval_ex(tmp, "inf", sizeof("inf"), element->inf);
278281
add_assoc_zval_ex(storage, md5str, 33, tmp);
@@ -288,6 +291,63 @@ static HashTable* spl_object_storage_debug_info(zval *obj, int *is_temp TSRMLS_D
288291
}
289292
/* }}} */
290293

294+
/* overriden for garbage collection
295+
* This is very hacky, but unfortunately the garbage collector can only query objects for
296+
* dependencies through get_properties */
297+
static HashTable *spl_object_storage_get_properties(zval *obj TSRMLS_DC) /* {{{ */
298+
{
299+
spl_SplObjectStorage *intern = (spl_SplObjectStorage*)zend_object_store_get_object(obj TSRMLS_CC);
300+
spl_SplObjectStorageElement *element;
301+
HashTable *props;
302+
HashPosition pos;
303+
zval *gcdata_arr = NULL,
304+
**gcdata_arr_pp;
305+
306+
props = std_object_handlers.get_properties(obj TSRMLS_CC);
307+
308+
if (!GC_G(gc_active)) {
309+
zend_hash_del(props, "\x00gcdata", sizeof("\x00gcdata"));
310+
return props;
311+
}
312+
313+
if (props->nApplyCount > 0) {
314+
return props;
315+
}
316+
317+
/* clean \x00gcdata, as it may be out of date */
318+
if (zend_hash_find(props, "\x00gcdata", sizeof("\x00gcdata"), (void**) &gcdata_arr_pp) == SUCCESS) {
319+
gcdata_arr = *gcdata_arr_pp;
320+
zend_hash_clean(Z_ARRVAL_P(gcdata_arr));
321+
}
322+
323+
/* destroy intern->debug_info, as it's holding references to the zvals */
324+
if (intern->debug_info != NULL) {
325+
zend_hash_destroy(intern->debug_info);
326+
efree(intern->debug_info);
327+
intern->debug_info = NULL;
328+
}
329+
330+
if (gcdata_arr == NULL) {
331+
MAKE_STD_ZVAL(gcdata_arr);
332+
array_init(gcdata_arr);
333+
/* don't decrease refcount of members when destroying */
334+
Z_ARRVAL_P(gcdata_arr)->pDestructor = NULL;
335+
336+
/* name starts with \x00 to make tampering in user-land more difficult */
337+
zend_hash_add(props, "\x00gcdata", sizeof("\x00gcdata"), &gcdata_arr, sizeof(gcdata_arr), NULL);
338+
}
339+
340+
zend_hash_internal_pointer_reset_ex(&intern->storage, &pos);
341+
while (zend_hash_get_current_data_ex(&intern->storage, (void **)&element, &pos) == SUCCESS) {
342+
add_next_index_zval(gcdata_arr, element->obj);
343+
add_next_index_zval(gcdata_arr, element->inf);
344+
zend_hash_move_forward_ex(&intern->storage, &pos);
345+
}
346+
347+
return props;
348+
}
349+
/* }}} */
350+
291351
static int spl_object_storage_compare_info(spl_SplObjectStorageElement *e1, spl_SplObjectStorageElement *e2 TSRMLS_DC) /* {{{ */
292352
{
293353
zval result;
@@ -1064,6 +1124,7 @@ PHP_MINIT_FUNCTION(spl_observer)
10641124
REGISTER_SPL_STD_CLASS_EX(SplObjectStorage, spl_SplObjectStorage_new, spl_funcs_SplObjectStorage);
10651125
memcpy(&spl_handler_SplObjectStorage, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
10661126

1127+
spl_handler_SplObjectStorage.get_properties = spl_object_storage_get_properties;
10671128
spl_handler_SplObjectStorage.get_debug_info = spl_object_storage_debug_info;
10681129
spl_handler_SplObjectStorage.compare_objects = spl_object_storage_compare_objects;
10691130
spl_handler_SplObjectStorage.clone_obj = spl_object_storage_clone;

ext/spl/tests/bug53071.phpt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Bug #53071 (Usage of SPLObjectStorage defeats gc_collect_cycles)
3+
--FILE--
4+
<?php
5+
class myClass
6+
{
7+
public $member;
8+
}
9+
function LimitedScope()
10+
{
11+
$myA = new myClass();
12+
$myB = new SplObjectStorage();
13+
$myC = new myClass();
14+
$myC->member = $myA; // myC has a referece to myA
15+
$myB->Attach($myC); // myB attaches myC
16+
$myA->member = $myB; // myA has myB, comleting the cycle
17+
}
18+
LimitedScope();
19+
var_dump(gc_collect_cycles());
20+
21+
echo "Done.\n";
22+
23+
?>
24+
--EXPECTF--
25+
int(5)
26+
Done.

0 commit comments

Comments
 (0)