Skip to content

Commit a431df8

Browse files
committed
Fix use-after-free on object released in hook
Fixes GH-16040
1 parent 6946bbc commit a431df8

File tree

2 files changed

+37
-22
lines changed

2 files changed

+37
-22
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
GH-16040: Use-after-free on object released in hook
3+
--FILE--
4+
<?php
5+
6+
class A {
7+
public $bar {
8+
get {
9+
$GLOBALS['a'] = null;
10+
return 42;
11+
}
12+
}
13+
}
14+
15+
$a = new A();
16+
var_dump($a->bar);
17+
18+
?>
19+
--EXPECT--
20+
int(42)

Zend/zend_object_handlers.c

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -684,22 +684,6 @@ static ZEND_COLD void zend_throw_no_prop_backing_value_access(zend_string *class
684684
ZSTR_VAL(class_name), ZSTR_VAL(prop_name));
685685
}
686686

687-
static bool zend_call_get_hook(
688-
const zend_property_info *prop_info, zend_string *prop_name,
689-
zend_function *get, zend_object *zobj, zval *rv)
690-
{
691-
if (!zend_should_call_hook(prop_info, zobj)) {
692-
if (UNEXPECTED(prop_info->flags & ZEND_ACC_VIRTUAL)) {
693-
zend_throw_no_prop_backing_value_access(zobj->ce->name, prop_name, /* is_read */ true);
694-
}
695-
return false;
696-
}
697-
698-
zend_call_known_instance_method_with_0_params(get, zobj, rv);
699-
700-
return true;
701-
}
702-
703687
ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int type, void **cache_slot, zval *rv) /* {{{ */
704688
{
705689
zval *retval;
@@ -808,8 +792,9 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
808792

809793
zend_class_entry *ce = zobj->ce;
810794

811-
if (!zend_call_get_hook(prop_info, name, get, zobj, rv)) {
812-
if (EG(exception)) {
795+
if (!zend_should_call_hook(prop_info, zobj)) {
796+
if (UNEXPECTED(prop_info->flags & ZEND_ACC_VIRTUAL)) {
797+
zend_throw_no_prop_backing_value_access(zobj->ce->name, name, /* is_read */ true);
813798
return &EG(uninitialized_zval);
814799
}
815800

@@ -826,12 +811,19 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
826811
goto try_again;
827812
}
828813

829-
if (EXPECTED(cache_slot
814+
/* Compute simple get before calling hook, as the hook may release zobj. */
815+
bool cache_simple_get = cache_slot
830816
&& zend_execute_ex == execute_ex
831817
&& zobj->ce->default_object_handlers->read_property == zend_std_read_property
832818
&& !zobj->ce->create_object
833819
&& !zend_is_in_hook(prop_info)
834-
&& !(prop_info->hooks[ZEND_PROPERTY_HOOK_GET]->common.fn_flags & ZEND_ACC_RETURN_REFERENCE))) {
820+
&& !(prop_info->hooks[ZEND_PROPERTY_HOOK_GET]->common.fn_flags & ZEND_ACC_RETURN_REFERENCE);
821+
822+
zend_call_known_instance_method_with_0_params(get, zobj, rv);
823+
824+
/* Only cache simple get after calling the hook to provide better error
825+
* messages for accidentally recursive hooks. */
826+
if (EXPECTED(cache_simple_get)) {
835827
ZEND_SET_PROPERTY_HOOK_SIMPLE_GET(cache_slot);
836828
}
837829

@@ -2229,14 +2221,17 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has
22292221
}
22302222

22312223
zval rv;
2232-
if (!zend_call_get_hook(prop_info, name, get, zobj, &rv)) {
2233-
if (EG(exception)) {
2224+
if (!zend_should_call_hook(prop_info, zobj)) {
2225+
if (UNEXPECTED(prop_info->flags & ZEND_ACC_VIRTUAL)) {
2226+
zend_throw_no_prop_backing_value_access(zobj->ce->name, name, /* is_read */ true);
22342227
return 0;
22352228
}
22362229
property_offset = prop_info->offset;
22372230
goto try_again;
22382231
}
22392232

2233+
zend_call_known_instance_method_with_0_params(get, zobj, &rv);
2234+
22402235
if (has_set_exists == ZEND_PROPERTY_NOT_EMPTY) {
22412236
result = zend_is_true(&rv);
22422237
} else {

0 commit comments

Comments
 (0)