Skip to content

Fix use-after-free of object through __isset() and globals #18852

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Zend/tests/gh18845.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
--TEST--
GH-18845: Use-after-free of object through __isset() and globals
--FILE--
<?php

#[AllowDynamicProperties]
class C {
public $dummy;
public function __isset($x) {
$GLOBALS['c'] = null;
return true;
}
}

$c = new C;
var_dump($c->prop ?? 1);

$r = new ReflectionClass(C::class);
$c = $r->newLazyProxy(function () {
$c = new C();
$c->prop = 2;
return $c;
});
var_dump($c->prop ?? 1);

?>
--EXPECT--
int(1)
int(2)
23 changes: 19 additions & 4 deletions Zend/zend_object_handlers.c
Original file line number Diff line number Diff line change
@@ -733,6 +733,8 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
uintptr_t property_offset;
const zend_property_info *prop_info = NULL;
uint32_t *guard = NULL;
bool obj_needs_deref = false;
zend_object *prev_zobj;

#if DEBUG_OBJECT_HANDLERS
fprintf(stderr, "Read object #%d property: %s\n", zobj->handle, ZSTR_VAL(name));
@@ -905,7 +907,9 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
if (zobj->ce->__get && !((*guard) & IN_GET)) {
goto call_getter;
}
OBJ_RELEASE(zobj);

obj_needs_deref = true;
prev_zobj = zobj;
} else if (zobj->ce->__get && !((*guard) & IN_GET)) {
goto call_getter_addref;
}
@@ -954,7 +958,7 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
zobj = zend_lazy_object_init(zobj);
if (!zobj) {
retval = &EG(uninitialized_zval);
goto exit;
goto exit_slow;
}

if (UNEXPECTED(guard)) {
@@ -965,11 +969,12 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
(*guard) |= guard_type;
retval = zend_std_read_property(zobj, name, type, cache_slot, rv);
(*guard) &= ~guard_type;
return retval;
goto exit_slow;
}
}

return zend_std_read_property(zobj, name, type, cache_slot, rv);
retval = zend_std_read_property(zobj, name, type, cache_slot, rv);
goto exit_slow;
}
}
if (type != BP_VAR_IS) {
@@ -981,6 +986,16 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
}
retval = &EG(uninitialized_zval);

exit_slow:
if (obj_needs_deref) {
/* Move value to rv in case zobj gets destroyed. */
if (retval != rv) {
ZVAL_COPY(rv, retval);
retval = rv;
}
OBJ_RELEASE(prev_zobj);
}

exit:
return retval;
}