Skip to content

Commit 46ee0fb

Browse files
authored
Disallow indirect modification on readonly properties within __clone() (#15012)
Indirect modification isn't allowed in __construct() because it allows references to leak, so it doesn't make much sense to allow it in __clone().
1 parent 7a2d5ef commit 46ee0fb

File tree

7 files changed

+44
-80
lines changed

7 files changed

+44
-80
lines changed

UPGRADING

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ PHP 8.4 UPGRADE NOTES
3333
now 13 bytes longer. Total length is platform-dependent.
3434
. Encountering recursion during comparison now results in a Error exception,
3535
rather than a fatal error.
36+
. Indirect modification of readonly properties within __clone() is no longer
37+
allowed, e.g. $ref = &$this->readonly. This was already forbidden for
38+
readonly initialization, and was an oversight in the "readonly
39+
reinitialization during cloning" implementation.
3640

3741
- DOM:
3842
. Added DOMNode::compareDocumentPosition() and DOMNode::DOCUMENT_POSITION_*

Zend/tests/readonly_props/readonly_clone_error5.phpt

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,8 @@ try {
5757
}
5858

5959
?>
60-
--EXPECTF--
61-
object(TestSetOnce)#%d (1) {
62-
["prop"]=>
63-
array(1) {
64-
[0]=>
65-
int(1)
66-
}
67-
}
68-
object(TestSetOnce)#%d (1) {
69-
["prop"]=>
70-
array(1) {
71-
[0]=>
72-
int(1)
73-
}
74-
}
60+
--EXPECT--
61+
Cannot indirectly modify readonly property TestSetOnce::$prop
62+
Cannot indirectly modify readonly property TestSetOnce::$prop
7563
Cannot indirectly modify readonly property TestSetTwice::$prop
7664
Cannot indirectly modify readonly property TestSetTwice::$prop
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
__clone() cannot indirectly modify unlocked readonly properties
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public function __construct(
8+
public readonly array $bar
9+
) {}
10+
11+
public function __clone()
12+
{
13+
$this->bar['bar'] = 'bar';
14+
}
15+
}
16+
17+
$foo = new Foo([]);
18+
// First call fills the cache slot
19+
try {
20+
var_dump(clone $foo);
21+
} catch (Error $e) {
22+
echo $e->getMessage(), "\n";
23+
}
24+
try {
25+
var_dump(clone $foo);
26+
} catch (Error $e) {
27+
echo $e->getMessage(), "\n";
28+
}
29+
30+
?>
31+
--EXPECT--
32+
Cannot indirectly modify readonly property Foo::$bar
33+
Cannot indirectly modify readonly property Foo::$bar

Zend/tests/readonly_props/readonly_clone_success3.phpt

Lines changed: 0 additions & 37 deletions
This file was deleted.

Zend/zend_execute.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3364,8 +3364,6 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c
33643364
ZEND_ASSERT(type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET);
33653365
if (Z_TYPE_P(ptr) == IS_OBJECT) {
33663366
ZVAL_COPY(result, ptr);
3367-
} else if (Z_PROP_FLAG_P(ptr) & IS_PROP_REINITABLE) {
3368-
Z_PROP_FLAG_P(ptr) &= ~IS_PROP_REINITABLE;
33693367
} else {
33703368
zend_readonly_property_indirect_modification_error(prop_info);
33713369
ZVAL_ERROR(result);

Zend/zend_object_handlers.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -696,8 +696,6 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
696696
* to make sure no actual modification is possible. */
697697
ZVAL_COPY(rv, retval);
698698
retval = rv;
699-
} else if (Z_PROP_FLAG_P(retval) & IS_PROP_REINITABLE) {
700-
Z_PROP_FLAG_P(retval) &= ~IS_PROP_REINITABLE;
701699
} else {
702700
zend_readonly_property_indirect_modification_error(prop_info);
703701
retval = &EG(uninitialized_zval);

ext/opcache/jit/zend_jit_ir.c

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13954,46 +13954,32 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit,
1395413954

1395513955
ir_IF_FALSE_cold(if_prop_obj);
1395613956

13957-
ir_ref extra_addr = ir_ADD_OFFSET(jit_ZVAL_ADDR(jit, prop_addr), offsetof(zval, u2.extra));
13958-
ir_ref extra = ir_LOAD_U32(extra_addr);
13959-
ir_ref if_reinitable = ir_IF(ir_AND_U32(extra, ir_CONST_U32(IS_PROP_REINITABLE)));
13960-
ir_IF_TRUE(if_reinitable);
13961-
ir_STORE(extra_addr, ir_AND_U32(extra, ir_CONST_U32(~IS_PROP_REINITABLE)));
13962-
ir_ref reinit_path = ir_END();
13963-
13964-
ir_IF_FALSE(if_reinitable);
13965-
1396613957
jit_SET_EX_OPLINE(jit, opline);
1396713958
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_readonly_property_indirect_modification_error), prop_info_ref);
1396813959
jit_set_Z_TYPE_INFO(jit, res_addr, _IS_ERROR);
1396913960
ir_END_list(end_inputs);
1397013961

1397113962
if (flags == ZEND_FETCH_DIM_WRITE) {
1397213963
ir_IF_FALSE_cold(if_readonly);
13973-
ir_MERGE_WITH(reinit_path);
1397413964
jit_SET_EX_OPLINE(jit, opline);
1397513965
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_check_array_promotion),
1397613966
prop_ref, prop_info_ref);
1397713967
ir_END_list(end_inputs);
1397813968
ir_IF_FALSE(if_has_prop_info);
1397913969
} else if (flags == ZEND_FETCH_REF) {
1398013970
ir_IF_FALSE_cold(if_readonly);
13981-
ir_MERGE_WITH(reinit_path);
1398213971
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_create_typed_ref),
1398313972
prop_ref,
1398413973
prop_info_ref,
1398513974
jit_ZVAL_ADDR(jit, res_addr));
1398613975
ir_END_list(end_inputs);
1398713976
ir_IF_FALSE(if_has_prop_info);
1398813977
} else {
13989-
ir_ref list = reinit_path;
13990-
1399113978
ZEND_ASSERT(flags == 0);
1399213979
ir_IF_FALSE(if_has_prop_info);
13993-
ir_END_list(list);
13980+
ir_ref no_prop_info_path = ir_END();
1399413981
ir_IF_FALSE(if_readonly);
13995-
ir_END_list(list);
13996-
ir_MERGE_list(list);
13982+
ir_MERGE_WITH(no_prop_info_path);
1399713983
}
1399813984
}
1399913985
} else {
@@ -14031,19 +14017,12 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit,
1403114017
ir_END_list(end_inputs);
1403214018

1403314019
ir_IF_FALSE_cold(if_prop_obj);
14034-
14035-
ir_ref extra_addr = ir_ADD_OFFSET(jit_ZVAL_ADDR(jit, prop_addr), offsetof(zval, u2.extra));
14036-
ir_ref extra = ir_LOAD_U32(extra_addr);
14037-
ir_ref if_reinitable = ir_IF(ir_AND_U32(extra, ir_CONST_U32(IS_PROP_REINITABLE)));
14038-
14039-
ir_IF_FALSE(if_reinitable);
1404014020
jit_SET_EX_OPLINE(jit, opline);
1404114021
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_readonly_property_indirect_modification_error), ir_CONST_ADDR(prop_info));
1404214022
jit_set_Z_TYPE_INFO(jit, res_addr, _IS_ERROR);
1404314023
ir_END_list(end_inputs);
1404414024

14045-
ir_IF_TRUE(if_reinitable);
14046-
ir_STORE(extra_addr, ir_AND_U32(extra, ir_CONST_U32(~IS_PROP_REINITABLE)));
14025+
goto result_fetched;
1404714026
}
1404814027

1404914028
if (opline->opcode == ZEND_FETCH_OBJ_W
@@ -14117,6 +14096,7 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit,
1411714096
}
1411814097
}
1411914098

14099+
result_fetched:
1412014100
if (op1_avoid_refcounting) {
1412114101
SET_STACK_REG(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(opline->op1.var), ZREG_NONE);
1412214102
}

0 commit comments

Comments
 (0)