diff --git a/Zend/tests/gh21776.phpt b/Zend/tests/gh21776.phpt new file mode 100644 index 000000000000..4f6ae956a226 --- /dev/null +++ b/Zend/tests/gh21776.phpt @@ -0,0 +1,16 @@ +--TEST-- +GH-21776 (Heap use-after-free in zend_object_is_lazy via magic __isset) +--FILE-- +a ?? 0; +echo "OK\n"; +?> +--EXPECT-- +OK diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 571aa9e92abc..f9fa55d80924 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -743,6 +743,7 @@ 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 release_zobj = false; #if DEBUG_OBJECT_HANDLERS fprintf(stderr, "Read object #%d property: %s\n", zobj->handle, ZSTR_VAL(name)); @@ -937,7 +938,7 @@ 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); + release_zobj = true; } else if (zobj->ce->__get && !((*guard) & IN_GET)) { goto call_getter_addref; } @@ -986,7 +987,7 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int zend_object *instance = zend_lazy_object_init(zobj); if (!instance) { retval = &EG(uninitialized_zval); - goto exit; + goto release_zobj_exit; } if (UNEXPECTED(guard && (instance->ce->ce_flags & ZEND_ACC_USE_GUARDS))) { @@ -999,11 +1000,12 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int (*guard) |= guard_type; retval = zend_std_read_property(instance, name, type, cache_slot, rv); (*guard) &= ~guard_type; - return retval; + goto release_zobj_exit; } } - return zend_std_read_property(instance, name, type, cache_slot, rv); + retval = zend_std_read_property(instance, name, type, cache_slot, rv); + goto release_zobj_exit; } } if (type != BP_VAR_IS) { @@ -1015,6 +1017,10 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int } retval = &EG(uninitialized_zval); +release_zobj_exit: + if (release_zobj) { + OBJ_RELEASE(zobj); + } exit: return retval; }