Skip to content
Draft
Show file tree
Hide file tree
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
32 changes: 32 additions & 0 deletions Zend/tests/gh15938-001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--TEST--
GH-15938 001: ASSIGN_OBJ_OP: Dynamic property may be undef by __toString(), leaking the new value
--ENV--
LEN=10
--FILE--
<?php

#[AllowDynamicProperties]
class C {
public $a;
}

$obj = new C();
$obj->b = str_repeat('c', (int)getenv('LEN'));
$obj->b .= new class {
function __toString() {
global $obj;
unset($obj->b);
return str_repeat('d', (int)getenv('LEN'));
}
};

var_dump($obj);

?>
==DONE==
--EXPECTF--
object(C)#%d (1) {
["a"]=>
NULL
}
==DONE==
51 changes: 51 additions & 0 deletions Zend/tests/gh15938-002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
--TEST--
GH-15938 002: ASSIGN_OBJ_OP: Properties ht may be resized by __toString(), write happens on freed buckets
--FILE--
<?php

#[AllowDynamicProperties]
class C {
public $a;
}

$obj = new C();
$obj->b = '';

$obj->b .= new class {
function __toString() {
global $obj;
for ($i = 0; $i < 8; $i++) {
$obj->{$i} = 0;
}
return 'str';
}
};

var_dump($obj);

?>
==DONE==
--EXPECTF--
object(C)#%d (10) {
["a"]=>
NULL
["b"]=>
string(0) ""
["0"]=>
int(0)
["1"]=>
int(0)
["2"]=>
int(0)
["3"]=>
int(0)
["4"]=>
int(0)
["5"]=>
int(0)
["6"]=>
int(0)
["7"]=>
int(0)
}
==DONE==
26 changes: 26 additions & 0 deletions Zend/tests/gh15938-003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
GH-15938 003: ASSIGN_OBJ_OP: Object may be freed by __toString(), write happens on freed object
--FILE--
<?php

class C {
public $a;
}

$obj = new C();
$obj->a = '';
$obj->a .= new class {
function __toString() {
global $obj;
$obj = null;
return 'str';
}
};

var_dump($obj);

?>
==DONE==
--EXPECT--
NULL
==DONE==
29 changes: 29 additions & 0 deletions Zend/tests/gh15938-004.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
--TEST--
GH-15938 001: ASSIGN_OBJ_OP: Property ht may be freed by resetAsLazy*(), write happens on freed ht
--FILE--
<?php

#[AllowDynamicProperties]
class C {
public $a;
}

$obj = new C();
$obj->b = str_repeat('a', 10);
$obj->b .= new class {
function __toString() {
global $obj;
$rc = new ReflectionClass($obj::class);
$rc->resetAsLazyGhost($obj, function () {});
return 'str';
}
};

var_dump($obj);

?>
==DONE==
--EXPECTF--
lazy ghost object(C)#%d (0) {
}
==DONE==
2 changes: 1 addition & 1 deletion Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -3612,7 +3612,7 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c
} else {
name = zval_get_tmp_string(prop_ptr, &tmp_name);
}
ptr = zobj->handlers->get_property_ptr_ptr(zobj, name, type, cache_slot);
ptr = zobj->handlers->get_property_ptr_ptr(zobj, name, type, cache_slot, NULL);
if (NULL == ptr) {
ptr = zobj->handlers->read_property(zobj, name, type, cache_slot, result);
if (ptr == result) {
Expand Down
12 changes: 9 additions & 3 deletions Zend/zend_object_handlers.c
Original file line number Diff line number Diff line change
Expand Up @@ -1378,7 +1378,7 @@ ZEND_API int zend_std_has_dimension(zend_object *object, zval *offset, int check
}
/* }}} */

ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *name, int type, void **cache_slot) /* {{{ */
ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *name, int type, void **cache_slot, zend_refcounted **container) /* {{{ */
{
zval *retval = NULL;
uintptr_t property_offset;
Expand All @@ -1402,7 +1402,7 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam
return &EG(error_zval);
}

return zend_std_get_property_ptr_ptr(zobj, name, type, cache_slot);
return zend_std_get_property_ptr_ptr(zobj, name, type, cache_slot, container);
}
if (UNEXPECTED(type == BP_VAR_RW || type == BP_VAR_R)) {
if (prop_info) {
Expand Down Expand Up @@ -1440,6 +1440,9 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam
zobj->properties = zend_array_dup(zobj->properties);
}
if (EXPECTED((retval = zend_hash_find(zobj->properties, name)) != NULL)) {
if (container) {
*container = (zend_refcounted*)zobj->properties;
}
return retval;
}
}
Expand All @@ -1460,7 +1463,7 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam
return &EG(error_zval);
}

return zend_std_get_property_ptr_ptr(zobj, name, type, cache_slot);
return zend_std_get_property_ptr_ptr(zobj, name, type, cache_slot, container);
}
if (UNEXPECTED(!zobj->properties)) {
rebuild_object_properties_internal(zobj);
Expand All @@ -1474,6 +1477,9 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam
retval = &EG(error_zval);
}

if (container) {
*container = (zend_refcounted*)zobj;
}
return retval;
}
/* }}} */
Expand Down
7 changes: 5 additions & 2 deletions Zend/zend_object_handlers.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,11 @@ typedef void (*zend_object_write_dimension_t)(zend_object *object, zval *offset,
* * &EG(error_zval), if an exception has been thrown.
* * NULL, if acquiring a direct pointer is not possible.
* In this case, the VM will fall back to using read_property and write_property.
* If 'container' is not NULL, it is set to the actual container of the zval
* pointer. The pointer remains valid as long as a reference on the container is
* held by the caller.
*/
typedef zval *(*zend_object_get_property_ptr_ptr_t)(zend_object *object, zend_string *member, int type, void **cache_slot);
typedef zval *(*zend_object_get_property_ptr_ptr_t)(zend_object *object, zend_string *member, int type, void **cache_slot, zend_refcounted **container);

/* Used to check if a property of the object exists */
/* param has_set_exists:
Expand Down Expand Up @@ -259,7 +262,7 @@ ZEND_API HashTable *zend_get_properties_no_lazy_init(zend_object *zobj);
ZEND_API HashTable *zend_std_get_gc(zend_object *object, zval **table, int *n);
ZEND_API HashTable *zend_std_get_debug_info(zend_object *object, int *is_temp);
ZEND_API zend_result zend_std_cast_object_tostring(zend_object *object, zval *writeobj, int type);
ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *object, zend_string *member, int type, void **cache_slot);
ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *object, zend_string *member, int type, void **cache_slot, zend_refcounted **container);
ZEND_API zval *zend_std_read_property(zend_object *object, zend_string *member, int type, void **cache_slot, zval *rv);
ZEND_API zval *zend_std_write_property(zend_object *object, zend_string *member, zval *value, void **cache_slot);
ZEND_API int zend_std_has_property(zend_object *object, zend_string *member, int has_set_exists, void **cache_slot);
Expand Down
11 changes: 8 additions & 3 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -1051,14 +1051,17 @@ ZEND_VM_C_LABEL(assign_op_object):
}
}
cache_slot = (OP2_TYPE == IS_CONST) ? CACHE_ADDR((opline+1)->extended_value) : _cache_slot;
if (EXPECTED((zptr = zobj->handlers->get_property_ptr_ptr(zobj, name, BP_VAR_RW, cache_slot)) != NULL)) {
zend_refcounted *container;
if (EXPECTED((zptr = zobj->handlers->get_property_ptr_ptr(zobj, name, BP_VAR_RW, cache_slot, &container)) != NULL)) {
if (UNEXPECTED(Z_ISERROR_P(zptr))) {
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
ZVAL_NULL(EX_VAR(opline->result.var));
}
} else {
zend_reference *ref;

GC_ADDREF(container);

do {
if (UNEXPECTED(Z_ISREF_P(zptr))) {
ref = Z_REF_P(zptr);
Expand All @@ -1081,6 +1084,8 @@ ZEND_VM_C_LABEL(assign_op_object):
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
ZVAL_COPY(EX_VAR(opline->result.var), zptr);
}

GC_DTOR(container);
}
} else {
zend_assign_op_overloaded_property(zobj, name, cache_slot, value OPLINE_CC EXECUTE_DATA_CC);
Expand Down Expand Up @@ -1328,7 +1333,7 @@ ZEND_VM_C_LABEL(pre_incdec_object):
}
}
cache_slot = (OP2_TYPE == IS_CONST) ? CACHE_ADDR(opline->extended_value) : _cache_slot;
if (EXPECTED((zptr = zobj->handlers->get_property_ptr_ptr(zobj, name, BP_VAR_RW, cache_slot)) != NULL)) {
if (EXPECTED((zptr = zobj->handlers->get_property_ptr_ptr(zobj, name, BP_VAR_RW, cache_slot, NULL)) != NULL)) {
if (UNEXPECTED(Z_ISERROR_P(zptr))) {
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
ZVAL_NULL(EX_VAR(opline->result.var));
Expand Down Expand Up @@ -1398,7 +1403,7 @@ ZEND_VM_C_LABEL(post_incdec_object):
}
}
cache_slot = (OP2_TYPE == IS_CONST) ? CACHE_ADDR(opline->extended_value) : _cache_slot;
if (EXPECTED((zptr = zobj->handlers->get_property_ptr_ptr(zobj, name, BP_VAR_RW, cache_slot)) != NULL)) {
if (EXPECTED((zptr = zobj->handlers->get_property_ptr_ptr(zobj, name, BP_VAR_RW, cache_slot, NULL)) != NULL)) {
if (UNEXPECTED(Z_ISERROR_P(zptr))) {
ZVAL_NULL(EX_VAR(opline->result.var));
} else {
Expand Down
Loading