Skip to content

Commit 34f1c41

Browse files
committed
Fix use-after-free during lazy object initialization
1 parent 090b53b commit 34f1c41

File tree

3 files changed

+97
-0
lines changed

3 files changed

+97
-0
lines changed

Zend/tests/lazy_objects/gh15999.phpt

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
--TEST--
2+
Lazy Objects: GH-15999: Object is released during initialization
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public $s;
8+
public function __destruct() {
9+
var_dump(__METHOD__);
10+
}
11+
}
12+
13+
print "# Ghost:\n";
14+
15+
$r = new ReflectionClass(C::class);
16+
17+
$o = $r->newLazyGhost(function ($obj) {
18+
global $o;
19+
$o = null;
20+
});
21+
22+
try {
23+
$o->s = $o;
24+
} catch (Error $e) {
25+
printf("%s: %s\n", $e::class, $e->getMessage());
26+
}
27+
28+
print "# Proxy:\n";
29+
30+
$o = $r->newLazyProxy(function ($obj) {
31+
global $o;
32+
$o = null;
33+
return new C();
34+
});
35+
36+
try {
37+
$o->s = $o;
38+
} catch (Error $e) {
39+
printf("%s: %s\n", $e::class, $e->getMessage());
40+
}
41+
42+
print "# GC cycle:\n";
43+
44+
$o = $r->newLazyGhost(function ($obj) {
45+
global $o;
46+
$o->s = $o;
47+
$o = null;
48+
gc_collect_cycles();
49+
});
50+
51+
$o->s = $o;
52+
gc_collect_cycles();
53+
54+
?>
55+
==DONE==
56+
--EXPECT--
57+
# Ghost:
58+
string(13) "C::__destruct"
59+
Error: Object was released during initialization
60+
# Proxy:
61+
string(13) "C::__destruct"
62+
Error: Object was released during initialization
63+
# GC cycle:
64+
string(13) "C::__destruct"
65+
==DONE==

Zend/zend_lazy_objects.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,9 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj)
429429
ZEND_ASSERT(zend_object_is_lazy_proxy(obj));
430430
ZEND_ASSERT(!zend_lazy_object_initialized(obj));
431431

432+
/* Prevent object from being released during initialization */
433+
GC_ADDREF(obj);
434+
432435
zend_lazy_object_info *info = zend_lazy_object_get_info(obj);
433436

434437
/* prevent reentrant initialization */
@@ -447,6 +450,7 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj)
447450

448451
if (UNEXPECTED(EG(exception))) {
449452
OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY;
453+
GC_DELREF(obj);
450454
return NULL;
451455
}
452456

@@ -456,6 +460,7 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj)
456460
ZSTR_VAL(obj->ce->name),
457461
zend_zval_value_name(&retval));
458462
zval_ptr_dtor(&retval);
463+
GC_DELREF(obj);
459464
return NULL;
460465

461466
}
@@ -466,13 +471,15 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj)
466471
zend_zval_value_name(&retval),
467472
ZSTR_VAL(obj->ce->name));
468473
zval_ptr_dtor(&retval);
474+
GC_DELREF(obj);
469475
return NULL;
470476
}
471477

472478
if (UNEXPECTED(Z_OBJ(retval) == obj || zend_object_is_lazy(Z_OBJ(retval)))) {
473479
OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY;
474480
zend_throw_error(NULL, "Lazy proxy factory must return a non-lazy object");
475481
zval_ptr_dtor(&retval);
482+
GC_DELREF(obj);
476483
return NULL;
477484
}
478485

@@ -495,6 +502,14 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj)
495502
}
496503
}
497504

505+
if (UNEXPECTED(GC_DELREF(obj) == 0)) {
506+
zend_throw_error(NULL, "Object was released during initialization");
507+
zend_objects_store_del(obj);
508+
return NULL;
509+
} else {
510+
gc_check_possible_root((zend_refcounted*) obj);
511+
}
512+
498513
return Z_OBJ(retval);
499514
}
500515

@@ -529,6 +544,9 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)
529544
return zend_lazy_object_init_proxy(obj);
530545
}
531546

547+
/* Prevent object from being released during initialization */
548+
GC_ADDREF(obj);
549+
532550
zend_fcall_info_cache *initializer = zend_lazy_object_get_initializer_fcc(obj);
533551

534552
/* Prevent reentrant initialization */
@@ -569,13 +587,15 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)
569587

570588
if (EG(exception)) {
571589
zend_lazy_object_revert_init(obj, properties_table_snapshot, properties_snapshot);
590+
GC_DELREF(obj);
572591
return NULL;
573592
}
574593

575594
if (Z_TYPE(retval) != IS_NULL) {
576595
zend_lazy_object_revert_init(obj, properties_table_snapshot, properties_snapshot);
577596
zval_ptr_dtor(&retval);
578597
zend_type_error("Lazy object initializer must return NULL or no value");
598+
GC_DELREF(obj);
579599
return NULL;
580600
}
581601

@@ -598,6 +618,14 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)
598618
* zend_lazy_object_has_stale_info() check */
599619
zend_lazy_object_del_info(obj);
600620

621+
if (UNEXPECTED(GC_DELREF(obj) == 0)) {
622+
zend_throw_error(NULL, "Object was released during initialization");
623+
zend_objects_store_del(obj);
624+
return NULL;
625+
} else {
626+
gc_check_possible_root((zend_refcounted*) obj);
627+
}
628+
601629
return obj;
602630
}
603631

Zend/zend_object_handlers.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,6 +1189,10 @@ found:;
11891189
variable_ptr = &EG(error_zval);
11901190
goto exit;
11911191
}
1192+
/* value may have changed during initialization */
1193+
if (UNEXPECTED(Z_ISREF_P(value))) {
1194+
value = Z_REFVAL_P(value);
1195+
}
11921196
return zend_std_write_property(zobj, name, value, cache_slot);
11931197
}
11941198
/* }}} */

0 commit comments

Comments
 (0)