Skip to content

Disallow indirect modification on readonly properties within __clone() #15012

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

Merged
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
4 changes: 4 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ PHP 8.4 UPGRADE NOTES
now 13 bytes longer. Total length is platform-dependent.
. Encountering recursion during comparison now results in a Error exception,
rather than a fatal error.
. Indirect modification of readonly properties within __clone() is no longer
allowed, e.g. $ref = &$this->readonly. This was already forbidden for
readonly initialization, and was an oversight in the "readonly
reinitialization during cloning" implementation.

- DOM:
. Added DOMNode::compareDocumentPosition() and DOMNode::DOCUMENT_POSITION_*
Expand Down
18 changes: 3 additions & 15 deletions Zend/tests/readonly_props/readonly_clone_error5.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -57,20 +57,8 @@ try {
}

?>
--EXPECTF--
object(TestSetOnce)#%d (1) {
["prop"]=>
array(1) {
[0]=>
int(1)
}
}
object(TestSetOnce)#%d (1) {
["prop"]=>
array(1) {
[0]=>
int(1)
}
}
--EXPECT--
Cannot indirectly modify readonly property TestSetOnce::$prop
Cannot indirectly modify readonly property TestSetOnce::$prop
Cannot indirectly modify readonly property TestSetTwice::$prop
Cannot indirectly modify readonly property TestSetTwice::$prop
33 changes: 33 additions & 0 deletions Zend/tests/readonly_props/readonly_clone_error7.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
--TEST--
__clone() cannot indirectly modify unlocked readonly properties
--FILE--
<?php

class Foo {
public function __construct(
public readonly array $bar
) {}

public function __clone()
{
$this->bar['bar'] = 'bar';
}
}

$foo = new Foo([]);
// First call fills the cache slot
try {
var_dump(clone $foo);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
var_dump(clone $foo);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECT--
Cannot indirectly modify readonly property Foo::$bar
Cannot indirectly modify readonly property Foo::$bar
37 changes: 0 additions & 37 deletions Zend/tests/readonly_props/readonly_clone_success3.phpt

This file was deleted.

2 changes: 0 additions & 2 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -3364,8 +3364,6 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c
ZEND_ASSERT(type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET);
if (Z_TYPE_P(ptr) == IS_OBJECT) {
ZVAL_COPY(result, ptr);
} else if (Z_PROP_FLAG_P(ptr) & IS_PROP_REINITABLE) {
Z_PROP_FLAG_P(ptr) &= ~IS_PROP_REINITABLE;
} else {
zend_readonly_property_indirect_modification_error(prop_info);
ZVAL_ERROR(result);
Expand Down
2 changes: 0 additions & 2 deletions Zend/zend_object_handlers.c
Original file line number Diff line number Diff line change
Expand Up @@ -696,8 +696,6 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
* to make sure no actual modification is possible. */
ZVAL_COPY(rv, retval);
retval = rv;
} else if (Z_PROP_FLAG_P(retval) & IS_PROP_REINITABLE) {
Z_PROP_FLAG_P(retval) &= ~IS_PROP_REINITABLE;
} else {
zend_readonly_property_indirect_modification_error(prop_info);
retval = &EG(uninitialized_zval);
Expand Down
28 changes: 4 additions & 24 deletions ext/opcache/jit/zend_jit_ir.c
Original file line number Diff line number Diff line change
Expand Up @@ -13954,46 +13954,32 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit,

ir_IF_FALSE_cold(if_prop_obj);

ir_ref extra_addr = ir_ADD_OFFSET(jit_ZVAL_ADDR(jit, prop_addr), offsetof(zval, u2.extra));
ir_ref extra = ir_LOAD_U32(extra_addr);
ir_ref if_reinitable = ir_IF(ir_AND_U32(extra, ir_CONST_U32(IS_PROP_REINITABLE)));
ir_IF_TRUE(if_reinitable);
ir_STORE(extra_addr, ir_AND_U32(extra, ir_CONST_U32(~IS_PROP_REINITABLE)));
ir_ref reinit_path = ir_END();

ir_IF_FALSE(if_reinitable);

jit_SET_EX_OPLINE(jit, opline);
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_readonly_property_indirect_modification_error), prop_info_ref);
jit_set_Z_TYPE_INFO(jit, res_addr, _IS_ERROR);
ir_END_list(end_inputs);

if (flags == ZEND_FETCH_DIM_WRITE) {
ir_IF_FALSE_cold(if_readonly);
ir_MERGE_WITH(reinit_path);
jit_SET_EX_OPLINE(jit, opline);
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_check_array_promotion),
prop_ref, prop_info_ref);
ir_END_list(end_inputs);
ir_IF_FALSE(if_has_prop_info);
} else if (flags == ZEND_FETCH_REF) {
ir_IF_FALSE_cold(if_readonly);
ir_MERGE_WITH(reinit_path);
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_create_typed_ref),
prop_ref,
prop_info_ref,
jit_ZVAL_ADDR(jit, res_addr));
ir_END_list(end_inputs);
ir_IF_FALSE(if_has_prop_info);
} else {
ir_ref list = reinit_path;

ZEND_ASSERT(flags == 0);
ir_IF_FALSE(if_has_prop_info);
ir_END_list(list);
ir_ref no_prop_info_path = ir_END();
ir_IF_FALSE(if_readonly);
ir_END_list(list);
ir_MERGE_list(list);
ir_MERGE_WITH(no_prop_info_path);
}
}
} else {
Expand Down Expand Up @@ -14031,19 +14017,12 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit,
ir_END_list(end_inputs);

ir_IF_FALSE_cold(if_prop_obj);

ir_ref extra_addr = ir_ADD_OFFSET(jit_ZVAL_ADDR(jit, prop_addr), offsetof(zval, u2.extra));
ir_ref extra = ir_LOAD_U32(extra_addr);
ir_ref if_reinitable = ir_IF(ir_AND_U32(extra, ir_CONST_U32(IS_PROP_REINITABLE)));

ir_IF_FALSE(if_reinitable);
jit_SET_EX_OPLINE(jit, opline);
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_readonly_property_indirect_modification_error), ir_CONST_ADDR(prop_info));
jit_set_Z_TYPE_INFO(jit, res_addr, _IS_ERROR);
ir_END_list(end_inputs);

ir_IF_TRUE(if_reinitable);
ir_STORE(extra_addr, ir_AND_U32(extra, ir_CONST_U32(~IS_PROP_REINITABLE)));
goto result_fetched;
}

if (opline->opcode == ZEND_FETCH_OBJ_W
Expand Down Expand Up @@ -14117,6 +14096,7 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit,
}
}

result_fetched:
if (op1_avoid_refcounting) {
SET_STACK_REG(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(opline->op1.var), ZREG_NONE);
}
Expand Down
Loading