Skip to content

Commit 4d447e1

Browse files
committed
Do not pass a CV slot to zend_assign_to_typed_ref_ex() with value_type=IS_TMP_VAR
zend_assign_to_typed_ref_ex() receives a zval slot and tries to dtor it when value_type=IS_TMP_VAR. This slot may be modified before dtor if it was in fact a CV slot. In particular it may be turned into a ref, replaced by anyother ref, or unset; leading to UAFs or leaks. Fix by passing a stack slot instead. This is consistent with zend_assign_to_typed_prop().
1 parent 1a54819 commit 4d447e1

File tree

6 files changed

+101
-13
lines changed

6 files changed

+101
-13
lines changed

Zend/tests/gh20316-001.phpt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--TEST--
2+
GH-20316 001: Assign to ref: non-ref rvalue may be turned into a ref
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public string $a = '';
8+
public $b;
9+
function __toString() {
10+
global $c; // turns rvalue into a ref
11+
return '';
12+
}
13+
}
14+
15+
for ($i = 0; $i < 2; $i++) {
16+
$c = new C;
17+
$c->b = &$c->a;
18+
$c->b = $c;
19+
20+
var_dump($c);
21+
unset($c);
22+
}
23+
24+
?>
25+
--EXPECTF--
26+
object(C)#%d (2) {
27+
["a"]=>
28+
&string(0) ""
29+
["b"]=>
30+
&string(0) ""
31+
}
32+
object(C)#%d (2) {
33+
["a"]=>
34+
&string(0) ""
35+
["b"]=>
36+
&string(0) ""
37+
}

Zend/tests/gh20316-002.phpt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
GH-20316 002: Assign to ref: ref rvalue may be unset
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public string $a = '';
8+
public $b;
9+
function __toString() {
10+
unset($GLOBALS['c']);
11+
unset($GLOBALS['r']);
12+
return '';
13+
}
14+
}
15+
16+
for ($i = 0; $i < 2; $i++) {
17+
$c = new C;
18+
$r = &$c;
19+
$c->b = &$c->a;
20+
$c->b = $c;
21+
22+
var_dump(isset($c));
23+
}
24+
25+
?>
26+
--EXPECT--
27+
bool(false)
28+
bool(false)

Zend/tests/gh20316-003.phpt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
GH-20316 003: Assign to ref: rvalue may be unset
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public string $a = '';
8+
public $b;
9+
function __toString() {
10+
unset($GLOBALS['c']);
11+
return '';
12+
}
13+
}
14+
15+
for ($i = 0; $i < 2; $i++) {
16+
$c = new C;
17+
$c->b = &$c->a;
18+
$c->b = $c;
19+
20+
var_dump(isset($c));
21+
}
22+
23+
?>
24+
--EXPECT--
25+
bool(false)
26+
bool(false)

Zend/zend_API.c

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5068,17 +5068,15 @@ ZEND_API zend_result zend_update_static_property_ex(zend_class_entry *scope, zen
50685068
}
50695069

50705070
ZEND_ASSERT(!Z_ISREF_P(value));
5071-
Z_TRY_ADDREF_P(value);
5071+
ZVAL_COPY(&tmp, value);
50725072
if (ZEND_TYPE_IS_SET(prop_info->type)) {
5073-
ZVAL_COPY_VALUE(&tmp, value);
50745073
if (!zend_verify_property_type(prop_info, &tmp, /* strict */ 0)) {
5075-
Z_TRY_DELREF_P(value);
5074+
zval_ptr_dtor(&tmp);
50765075
return FAILURE;
50775076
}
5078-
value = &tmp;
50795077
}
50805078

5081-
zend_assign_to_variable(property, value, IS_TMP_VAR, /* strict */ 0);
5079+
zend_assign_to_variable(property, &tmp, IS_TMP_VAR, /* strict */ 0);
50825080
return SUCCESS;
50835081
}
50845082
/* }}} */

Zend/zend_execute.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,8 +601,9 @@ static zend_never_inline ZEND_COLD zval *zend_wrong_assign_to_variable_reference
601601
return &EG(uninitialized_zval);
602602
}
603603

604+
zval tmp;
605+
ZVAL_COPY(&tmp, value_ptr);
604606
/* Use IS_TMP_VAR instead of IS_VAR to avoid ISREF check */
605-
Z_TRY_ADDREF_P(value_ptr);
606607
return zend_assign_to_variable_ex(variable_ptr, value_ptr, IS_TMP_VAR, EX_USES_STRICT_TYPES(), garbage_ptr);
607608
}
608609

Zend/zend_object_handlers.c

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,11 +1078,10 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva
10781078
}
10791079

10801080
if (Z_TYPE_P(variable_ptr) != IS_UNDEF) {
1081-
Z_TRY_ADDREF_P(value);
1081+
ZVAL_COPY(&tmp, value);
10821082

10831083
if (prop_info) {
10841084
typed_property:
1085-
ZVAL_COPY_VALUE(&tmp, value);
10861085
// Increase refcount to prevent object from being released in __toString()
10871086
GC_ADDREF(zobj);
10881087
bool type_matched = zend_verify_property_type(prop_info, &tmp, property_uses_strict_types());
@@ -1099,14 +1098,13 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva
10991098
goto exit;
11001099
}
11011100
Z_PROP_FLAG_P(variable_ptr) &= ~(IS_PROP_UNINIT|IS_PROP_REINITABLE);
1102-
value = &tmp;
11031101
}
11041102

11051103
found:;
11061104
zend_refcounted *garbage = NULL;
11071105

11081106
variable_ptr = zend_assign_to_variable_ex(
1109-
variable_ptr, value, IS_TMP_VAR, property_uses_strict_types(), &garbage);
1107+
variable_ptr, &tmp, IS_TMP_VAR, property_uses_strict_types(), &garbage);
11101108

11111109
if (garbage) {
11121110
if (GC_DELREF(garbage) == 0) {
@@ -1146,7 +1144,7 @@ found:;
11461144
zobj->properties = zend_array_dup(zobj->properties);
11471145
}
11481146
if ((variable_ptr = zend_hash_find(zobj->properties, name)) != NULL) {
1149-
Z_TRY_ADDREF_P(value);
1147+
ZVAL_COPY(&tmp, value);
11501148
goto found;
11511149
}
11521150
}
@@ -1241,12 +1239,12 @@ found:;
12411239
if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) {
12421240
variable_ptr = OBJ_PROP(zobj, property_offset);
12431241

1244-
Z_TRY_ADDREF_P(value);
12451242
if (prop_info) {
1243+
ZVAL_COPY(&tmp, value);
12461244
goto typed_property;
12471245
}
12481246

1249-
ZVAL_COPY_VALUE(variable_ptr, value);
1247+
ZVAL_COPY(variable_ptr, value);
12501248
} else {
12511249
if (UNEXPECTED(zobj->ce->ce_flags & ZEND_ACC_NO_DYNAMIC_PROPERTIES)) {
12521250
zend_forbidden_dynamic_property(zobj->ce, name);

0 commit comments

Comments
 (0)