diff --git a/Zend/tests/gh18845.phpt b/Zend/tests/gh18845.phpt new file mode 100644 index 000000000000..b887dcf5e1f7 --- /dev/null +++ b/Zend/tests/gh18845.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-18845: Use-after-free of object through __isset() and globals +--FILE-- +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) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 7b804e7afe95..46c08f1d1d7a 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -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; }