mirror of
https://github.com/php/php-src.git
synced 2025-08-15 13:38:49 +02:00
Merge branch 'PHP-8.4'
* PHP-8.4: [ci skip] NEWS for GH-16004 Fix use-after-free during lazy object initialization (#16004)
This commit is contained in:
commit
dbec59166f
4 changed files with 215 additions and 11 deletions
112
Zend/tests/lazy_objects/gh15999_001.phpt
Normal file
112
Zend/tests/lazy_objects/gh15999_001.phpt
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
--TEST--
|
||||||
|
Lazy Objects: GH-15999 001: Object is released during initialization
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class C {
|
||||||
|
public $s;
|
||||||
|
public function __destruct() {
|
||||||
|
var_dump(__METHOD__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print "# Ghost:\n";
|
||||||
|
|
||||||
|
$r = new ReflectionClass(C::class);
|
||||||
|
|
||||||
|
$o = $r->newLazyGhost(function ($obj) {
|
||||||
|
global $o;
|
||||||
|
$o = null;
|
||||||
|
});
|
||||||
|
$p = new stdClass;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$o->s = $p;
|
||||||
|
} catch (Error $e) {
|
||||||
|
printf("%s: %s\n", $e::class, $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
print "# Proxy:\n";
|
||||||
|
|
||||||
|
$o = $r->newLazyProxy(function ($obj) {
|
||||||
|
global $o;
|
||||||
|
$o = null;
|
||||||
|
return new C();
|
||||||
|
});
|
||||||
|
$p = new stdClass;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$o->s = $p;
|
||||||
|
} catch (Error $e) {
|
||||||
|
printf("%s: %s\n", $e::class, $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
print "# GC cycle:\n";
|
||||||
|
|
||||||
|
$o = $r->newLazyGhost(function ($obj) {
|
||||||
|
global $o;
|
||||||
|
$o->s = $o;
|
||||||
|
$o = null;
|
||||||
|
gc_collect_cycles();
|
||||||
|
});
|
||||||
|
$p = new stdClass;
|
||||||
|
|
||||||
|
$o->s = $p;
|
||||||
|
gc_collect_cycles();
|
||||||
|
|
||||||
|
print "# Nested error (ghost):\n";
|
||||||
|
|
||||||
|
$r = new ReflectionClass(C::class);
|
||||||
|
|
||||||
|
$o = $r->newLazyGhost(function ($obj) {
|
||||||
|
global $o;
|
||||||
|
$o = null;
|
||||||
|
return new stdClass;
|
||||||
|
});
|
||||||
|
$p = new stdClass;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$o->s = $p;
|
||||||
|
} catch (Error $e) {
|
||||||
|
do {
|
||||||
|
printf("%s: %s\n", $e::class, $e->getMessage());
|
||||||
|
} while ($e = $e->getPrevious());
|
||||||
|
}
|
||||||
|
|
||||||
|
print "# Nested error (proxy):\n";
|
||||||
|
|
||||||
|
$r = new ReflectionClass(C::class);
|
||||||
|
|
||||||
|
$o = $r->newLazyProxy(function ($obj) {
|
||||||
|
global $o;
|
||||||
|
$o = null;
|
||||||
|
return new stdClass;
|
||||||
|
});
|
||||||
|
$p = new stdClass;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$o->s = $p;
|
||||||
|
} catch (Error $e) {
|
||||||
|
do {
|
||||||
|
printf("%s: %s\n", $e::class, $e->getMessage());
|
||||||
|
} while ($e = $e->getPrevious());
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
==DONE==
|
||||||
|
--EXPECT--
|
||||||
|
# Ghost:
|
||||||
|
string(13) "C::__destruct"
|
||||||
|
Error: Lazy object was released during initialization
|
||||||
|
# Proxy:
|
||||||
|
string(13) "C::__destruct"
|
||||||
|
Error: Lazy object was released during initialization
|
||||||
|
# GC cycle:
|
||||||
|
string(13) "C::__destruct"
|
||||||
|
# Nested error (ghost):
|
||||||
|
Error: Lazy object was released during initialization
|
||||||
|
TypeError: Lazy object initializer must return NULL or no value
|
||||||
|
# Nested error (proxy):
|
||||||
|
Error: Lazy object was released during initialization
|
||||||
|
TypeError: The real instance class stdClass is not compatible with the proxy class C. The proxy must be a instance of the same class as the real instance, or a sub-class with no additional properties, and no overrides of the __destructor or __clone methods.
|
||||||
|
==DONE==
|
55
Zend/tests/lazy_objects/gh15999_002.phpt
Normal file
55
Zend/tests/lazy_objects/gh15999_002.phpt
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
--TEST--
|
||||||
|
Lazy Objects: GH-15999 002: Assigned value is changed during lazy object initialization
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class C {
|
||||||
|
public $s;
|
||||||
|
public function __destruct() {
|
||||||
|
var_dump(__METHOD__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print "# Ghost:\n";
|
||||||
|
|
||||||
|
$r = new ReflectionClass(C::class);
|
||||||
|
|
||||||
|
$o = $r->newLazyGhost(function ($obj) {
|
||||||
|
global $p;
|
||||||
|
$p = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
$p = new stdClass;
|
||||||
|
var_dump($o->s = $p);
|
||||||
|
var_dump($o->s);
|
||||||
|
|
||||||
|
print "# Proxy:\n";
|
||||||
|
|
||||||
|
$r = new ReflectionClass(C::class);
|
||||||
|
|
||||||
|
$o = $r->newLazyProxy(function ($obj) {
|
||||||
|
global $p;
|
||||||
|
$p = null;
|
||||||
|
return new C();
|
||||||
|
});
|
||||||
|
|
||||||
|
$p = new stdClass;
|
||||||
|
var_dump($o->s = $p);
|
||||||
|
var_dump($o->s);
|
||||||
|
|
||||||
|
?>
|
||||||
|
==DONE==
|
||||||
|
--EXPECTF--
|
||||||
|
# Ghost:
|
||||||
|
object(stdClass)#%d (0) {
|
||||||
|
}
|
||||||
|
object(stdClass)#%d (0) {
|
||||||
|
}
|
||||||
|
# Proxy:
|
||||||
|
string(13) "C::__destruct"
|
||||||
|
object(stdClass)#%d (0) {
|
||||||
|
}
|
||||||
|
object(stdClass)#%d (0) {
|
||||||
|
}
|
||||||
|
==DONE==
|
||||||
|
string(13) "C::__destruct"
|
|
@ -429,6 +429,9 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj)
|
||||||
ZEND_ASSERT(zend_object_is_lazy_proxy(obj));
|
ZEND_ASSERT(zend_object_is_lazy_proxy(obj));
|
||||||
ZEND_ASSERT(!zend_lazy_object_initialized(obj));
|
ZEND_ASSERT(!zend_lazy_object_initialized(obj));
|
||||||
|
|
||||||
|
/* Prevent object from being released during initialization */
|
||||||
|
GC_ADDREF(obj);
|
||||||
|
|
||||||
zend_lazy_object_info *info = zend_lazy_object_get_info(obj);
|
zend_lazy_object_info *info = zend_lazy_object_get_info(obj);
|
||||||
|
|
||||||
/* prevent reentrant initialization */
|
/* prevent reentrant initialization */
|
||||||
|
@ -440,6 +443,7 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj)
|
||||||
zval zobj;
|
zval zobj;
|
||||||
HashTable *named_params = NULL;
|
HashTable *named_params = NULL;
|
||||||
zend_fcall_info_cache *initializer = &info->u.initializer.fcc;
|
zend_fcall_info_cache *initializer = &info->u.initializer.fcc;
|
||||||
|
zend_object *instance = NULL;
|
||||||
|
|
||||||
ZVAL_OBJ(&zobj, obj);
|
ZVAL_OBJ(&zobj, obj);
|
||||||
|
|
||||||
|
@ -447,7 +451,7 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj)
|
||||||
|
|
||||||
if (UNEXPECTED(EG(exception))) {
|
if (UNEXPECTED(EG(exception))) {
|
||||||
OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY;
|
OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY;
|
||||||
return NULL;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UNEXPECTED(Z_TYPE(retval) != IS_OBJECT)) {
|
if (UNEXPECTED(Z_TYPE(retval) != IS_OBJECT)) {
|
||||||
|
@ -456,8 +460,7 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj)
|
||||||
ZSTR_VAL(obj->ce->name),
|
ZSTR_VAL(obj->ce->name),
|
||||||
zend_zval_value_name(&retval));
|
zend_zval_value_name(&retval));
|
||||||
zval_ptr_dtor(&retval);
|
zval_ptr_dtor(&retval);
|
||||||
return NULL;
|
goto exit;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UNEXPECTED(Z_TYPE(retval) != IS_OBJECT || !zend_lazy_object_compatible(Z_OBJ(retval), obj))) {
|
if (UNEXPECTED(Z_TYPE(retval) != IS_OBJECT || !zend_lazy_object_compatible(Z_OBJ(retval), obj))) {
|
||||||
|
@ -466,14 +469,14 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj)
|
||||||
zend_zval_value_name(&retval),
|
zend_zval_value_name(&retval),
|
||||||
ZSTR_VAL(obj->ce->name));
|
ZSTR_VAL(obj->ce->name));
|
||||||
zval_ptr_dtor(&retval);
|
zval_ptr_dtor(&retval);
|
||||||
return NULL;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UNEXPECTED(Z_OBJ(retval) == obj || zend_object_is_lazy(Z_OBJ(retval)))) {
|
if (UNEXPECTED(Z_OBJ(retval) == obj || zend_object_is_lazy(Z_OBJ(retval)))) {
|
||||||
OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY;
|
OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY;
|
||||||
zend_throw_error(NULL, "Lazy proxy factory must return a non-lazy object");
|
zend_throw_error(NULL, "Lazy proxy factory must return a non-lazy object");
|
||||||
zval_ptr_dtor(&retval);
|
zval_ptr_dtor(&retval);
|
||||||
return NULL;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
zend_fcc_dtor(&info->u.initializer.fcc);
|
zend_fcc_dtor(&info->u.initializer.fcc);
|
||||||
|
@ -495,7 +498,18 @@ static zend_object *zend_lazy_object_init_proxy(zend_object *obj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Z_OBJ(retval);
|
instance = Z_OBJ(retval);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
if (UNEXPECTED(GC_DELREF(obj) == 0)) {
|
||||||
|
zend_throw_error(NULL, "Lazy object was released during initialization");
|
||||||
|
zend_objects_store_del(obj);
|
||||||
|
instance = NULL;
|
||||||
|
} else {
|
||||||
|
gc_check_possible_root((zend_refcounted*) obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initialize a lazy object. */
|
/* Initialize a lazy object. */
|
||||||
|
@ -529,6 +543,9 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)
|
||||||
return zend_lazy_object_init_proxy(obj);
|
return zend_lazy_object_init_proxy(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Prevent object from being released during initialization */
|
||||||
|
GC_ADDREF(obj);
|
||||||
|
|
||||||
zend_fcall_info_cache *initializer = zend_lazy_object_get_initializer_fcc(obj);
|
zend_fcall_info_cache *initializer = zend_lazy_object_get_initializer_fcc(obj);
|
||||||
|
|
||||||
/* Prevent reentrant initialization */
|
/* Prevent reentrant initialization */
|
||||||
|
@ -562,6 +579,7 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)
|
||||||
int argc = 1;
|
int argc = 1;
|
||||||
zval zobj;
|
zval zobj;
|
||||||
HashTable *named_params = NULL;
|
HashTable *named_params = NULL;
|
||||||
|
zend_object *instance = NULL;
|
||||||
|
|
||||||
ZVAL_OBJ(&zobj, obj);
|
ZVAL_OBJ(&zobj, obj);
|
||||||
|
|
||||||
|
@ -569,14 +587,14 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)
|
||||||
|
|
||||||
if (EG(exception)) {
|
if (EG(exception)) {
|
||||||
zend_lazy_object_revert_init(obj, properties_table_snapshot, properties_snapshot);
|
zend_lazy_object_revert_init(obj, properties_table_snapshot, properties_snapshot);
|
||||||
return NULL;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Z_TYPE(retval) != IS_NULL) {
|
if (Z_TYPE(retval) != IS_NULL) {
|
||||||
zend_lazy_object_revert_init(obj, properties_table_snapshot, properties_snapshot);
|
zend_lazy_object_revert_init(obj, properties_table_snapshot, properties_snapshot);
|
||||||
zval_ptr_dtor(&retval);
|
zval_ptr_dtor(&retval);
|
||||||
zend_type_error("Lazy object initializer must return NULL or no value");
|
zend_type_error("Lazy object initializer must return NULL or no value");
|
||||||
return NULL;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (properties_table_snapshot) {
|
if (properties_table_snapshot) {
|
||||||
|
@ -598,7 +616,18 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *obj)
|
||||||
* zend_lazy_object_has_stale_info() check */
|
* zend_lazy_object_has_stale_info() check */
|
||||||
zend_lazy_object_del_info(obj);
|
zend_lazy_object_del_info(obj);
|
||||||
|
|
||||||
return obj;
|
instance = obj;
|
||||||
|
|
||||||
|
exit:
|
||||||
|
if (UNEXPECTED(GC_DELREF(obj) == 0)) {
|
||||||
|
zend_throw_error(NULL, "Lazy object was released during initialization");
|
||||||
|
zend_objects_store_del(obj);
|
||||||
|
instance = NULL;
|
||||||
|
} else {
|
||||||
|
gc_check_possible_root((zend_refcounted*) obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mark an object as non-lazy (after all properties were initialized) */
|
/* Mark an object as non-lazy (after all properties were initialized) */
|
||||||
|
|
|
@ -1183,13 +1183,21 @@ write_std_property:
|
||||||
exit:
|
exit:
|
||||||
return variable_ptr;
|
return variable_ptr;
|
||||||
|
|
||||||
lazy_init:
|
lazy_init:;
|
||||||
|
/* backup value as it may change during initialization */
|
||||||
|
zval backup;
|
||||||
|
ZVAL_COPY(&backup, value);
|
||||||
|
|
||||||
zobj = zend_lazy_object_init(zobj);
|
zobj = zend_lazy_object_init(zobj);
|
||||||
if (UNEXPECTED(!zobj)) {
|
if (UNEXPECTED(!zobj)) {
|
||||||
variable_ptr = &EG(error_zval);
|
variable_ptr = &EG(error_zval);
|
||||||
|
zval_ptr_dtor(&backup);
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
return zend_std_write_property(zobj, name, value, cache_slot);
|
|
||||||
|
variable_ptr = zend_std_write_property(zobj, name, &backup, cache_slot);
|
||||||
|
zval_ptr_dtor(&backup);
|
||||||
|
return variable_ptr;
|
||||||
}
|
}
|
||||||
/* }}} */
|
/* }}} */
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue