Skip to content

Commit e3179a0

Browse files
committed
Merge branch 'PHP-8.2'
* PHP-8.2: Fix GH-10168: heap-buffer-overflow at zval_undefined_cv
2 parents 3ff8333 + 972a5a0 commit e3179a0

File tree

7 files changed

+484
-193
lines changed

7 files changed

+484
-193
lines changed

Zend/tests/gh10168_1.phpt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
GH-10168 (heap-buffer-overflow at zval_undefined_cv): array variation
3+
--FILE--
4+
<?php
5+
6+
class Test
7+
{
8+
static $instances;
9+
public function __construct() {
10+
(self::$instances[NULL] = $this) > 0;
11+
var_dump(self::$instances);
12+
}
13+
14+
function __destruct() {
15+
unset(self::$instances[NULL]);
16+
}
17+
}
18+
new Test();
19+
new Test();
20+
21+
?>
22+
--EXPECTF--
23+
Notice: Object of class Test could not be converted to int in %s on line %d
24+
array(1) {
25+
[""]=>
26+
object(Test)#1 (0) {
27+
}
28+
}
29+
30+
Notice: Object of class Test could not be converted to int in %s on line %d
31+
array(0) {
32+
}

Zend/tests/gh10168_2.phpt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
GH-10168 (heap-buffer-overflow at zval_undefined_cv): assign global variation
3+
--FILE--
4+
<?php
5+
6+
$a = null;
7+
8+
class Test
9+
{
10+
public function __construct() {
11+
($GLOBALS['a'] = $this) > 0;
12+
// Destructor called after comparison, so a will be NULL
13+
var_dump($GLOBALS['a']);
14+
}
15+
16+
function __destruct() {
17+
unset($GLOBALS['a']);
18+
}
19+
}
20+
new Test();
21+
new Test();
22+
23+
?>
24+
--EXPECTF--
25+
Notice: Object of class Test could not be converted to int in %s on line %d
26+
object(Test)#1 (0) {
27+
}
28+
29+
Notice: Object of class Test could not be converted to int in %s on line %d
30+
31+
Warning: Undefined global variable $a in %s on line %d
32+
NULL

Zend/tests/gh10168_3.phpt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
GH-10168 (heap-buffer-overflow at zval_undefined_cv): assign typed prop
3+
--FILE--
4+
<?php
5+
class Test
6+
{
7+
static ?Test $a = null;
8+
9+
public function __construct() {
10+
if (self::$a === null) {
11+
var_dump(self::$a = &$this);
12+
} else {
13+
var_dump(self::$a = $this);
14+
}
15+
}
16+
17+
function __destruct() {
18+
self::$a = null;
19+
}
20+
}
21+
new Test();
22+
new Test();
23+
24+
?>
25+
--EXPECTF--
26+
object(Test)#1 (0) {
27+
}
28+
object(Test)#2 (0) {
29+
}

Zend/zend_execute.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3577,7 +3577,7 @@ static zend_always_inline void i_zval_ptr_dtor_noref(zval *zval_ptr) {
35773577
}
35783578
}
35793579

3580-
ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *orig_value, zend_uchar value_type, bool strict)
3580+
ZEND_API zval* zend_assign_to_typed_ref_and_result(zval *variable_ptr, zval *orig_value, zend_uchar value_type, bool strict, zval *result_variable_ptr)
35813581
{
35823582
bool ret;
35833583
zval value;
@@ -3597,6 +3597,9 @@ ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *orig_value, ze
35973597
} else {
35983598
zval_ptr_dtor_nogc(&value);
35993599
}
3600+
if (result_variable_ptr) {
3601+
ZVAL_COPY(result_variable_ptr, variable_ptr);
3602+
}
36003603
if (value_type & (IS_VAR|IS_TMP_VAR)) {
36013604
if (UNEXPECTED(ref)) {
36023605
if (UNEXPECTED(GC_DELREF(ref) == 0)) {
@@ -3610,6 +3613,11 @@ ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *orig_value, ze
36103613
return variable_ptr;
36113614
}
36123615

3616+
ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *orig_value, zend_uchar value_type, bool strict)
3617+
{
3618+
return zend_assign_to_typed_ref_and_result(variable_ptr, orig_value, value_type, strict, NULL);
3619+
}
3620+
36133621
ZEND_API bool ZEND_FASTCALL zend_verify_prop_assignable_by_ref_ex(const zend_property_info *prop_info, zval *orig_val, bool strict, zend_verify_prop_assignable_by_ref_context context) {
36143622
zval *val = orig_val;
36153623
if (Z_ISREF_P(val) && ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(val))) {

Zend/zend_execute.h

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ ZEND_API bool zend_verify_internal_return_type(zend_function *zf, zval *ret);
118118
ZEND_API void ZEND_FASTCALL zend_ref_add_type_source(zend_property_info_source_list *source_list, zend_property_info *prop);
119119
ZEND_API void ZEND_FASTCALL zend_ref_del_type_source(zend_property_info_source_list *source_list, const zend_property_info *prop);
120120

121+
ZEND_API zval* zend_assign_to_typed_ref_and_result(zval *variable_ptr, zval *orig_value, zend_uchar value_type, bool strict, zval *result_variable_ptr);
121122
ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *value, zend_uchar value_type, bool strict);
122123

123124
static zend_always_inline void zend_copy_to_variable(zval *variable_ptr, zval *value, zend_uchar value_type)
@@ -147,12 +148,22 @@ static zend_always_inline void zend_copy_to_variable(zval *variable_ptr, zval *v
147148
}
148149
}
149150

151+
static zend_always_inline void zend_handle_garbage_from_variable_assignment(zend_refcounted *garbage)
152+
{
153+
if (GC_DELREF(garbage) == 0) {
154+
rc_dtor_func(garbage);
155+
} else { /* we need to split */
156+
/* optimized version of GC_ZVAL_CHECK_POSSIBLE_ROOT(variable_ptr) */
157+
if (UNEXPECTED(GC_MAY_LEAK(garbage))) {
158+
gc_possible_root(garbage);
159+
}
160+
}
161+
}
162+
150163
static zend_always_inline zval* zend_assign_to_variable(zval *variable_ptr, zval *value, zend_uchar value_type, bool strict)
151164
{
152165
do {
153166
if (UNEXPECTED(Z_REFCOUNTED_P(variable_ptr))) {
154-
zend_refcounted *garbage;
155-
156167
if (Z_ISREF_P(variable_ptr)) {
157168
if (UNEXPECTED(ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(variable_ptr)))) {
158169
return zend_assign_to_typed_ref(variable_ptr, value, value_type, strict);
@@ -163,21 +174,42 @@ static zend_always_inline zval* zend_assign_to_variable(zval *variable_ptr, zval
163174
break;
164175
}
165176
}
166-
garbage = Z_COUNTED_P(variable_ptr);
177+
zend_refcounted *garbage = Z_COUNTED_P(variable_ptr);
167178
zend_copy_to_variable(variable_ptr, value, value_type);
168-
if (GC_DELREF(garbage) == 0) {
169-
rc_dtor_func(garbage);
170-
} else { /* we need to split */
171-
/* optimized version of GC_ZVAL_CHECK_POSSIBLE_ROOT(variable_ptr) */
172-
if (UNEXPECTED(GC_MAY_LEAK(garbage))) {
173-
gc_possible_root(garbage);
179+
zend_handle_garbage_from_variable_assignment(garbage);
180+
return variable_ptr;
181+
}
182+
} while (0);
183+
184+
zend_copy_to_variable(variable_ptr, value, value_type);
185+
return variable_ptr;
186+
}
187+
188+
static zend_always_inline zval* zend_assign_to_two_variables(zval *result_variable_ptr, zval *variable_ptr, zval *value, zend_uchar value_type, bool strict)
189+
{
190+
do {
191+
if (UNEXPECTED(Z_REFCOUNTED_P(variable_ptr))) {
192+
if (Z_ISREF_P(variable_ptr)) {
193+
if (UNEXPECTED(ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(variable_ptr)))) {
194+
variable_ptr = zend_assign_to_typed_ref_and_result(variable_ptr, value, value_type, strict, result_variable_ptr);
195+
return variable_ptr;
196+
}
197+
198+
variable_ptr = Z_REFVAL_P(variable_ptr);
199+
if (EXPECTED(!Z_REFCOUNTED_P(variable_ptr))) {
200+
break;
174201
}
175202
}
203+
zend_refcounted *garbage = Z_COUNTED_P(variable_ptr);
204+
zend_copy_to_variable(variable_ptr, value, value_type);
205+
ZVAL_COPY(result_variable_ptr, variable_ptr);
206+
zend_handle_garbage_from_variable_assignment(garbage);
176207
return variable_ptr;
177208
}
178209
} while (0);
179210

180211
zend_copy_to_variable(variable_ptr, value, value_type);
212+
ZVAL_COPY(result_variable_ptr, variable_ptr);
181213
return variable_ptr;
182214
}
183215

Zend/zend_vm_def.h

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2587,6 +2587,9 @@ ZEND_VM_C_LABEL(try_assign_dim_array):
25872587
Z_ADDREF_P(value);
25882588
}
25892589
}
2590+
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
2591+
ZVAL_COPY(EX_VAR(opline->result.var), value);
2592+
}
25902593
} else {
25912594
dim = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);
25922595
if (OP2_TYPE == IS_CONST) {
@@ -2598,10 +2601,11 @@ ZEND_VM_C_LABEL(try_assign_dim_array):
25982601
ZEND_VM_C_GOTO(assign_dim_error);
25992602
}
26002603
value = GET_OP_DATA_ZVAL_PTR(BP_VAR_R);
2601-
value = zend_assign_to_variable(variable_ptr, value, OP_DATA_TYPE, EX_USES_STRICT_TYPES());
2602-
}
2603-
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
2604-
ZVAL_COPY(EX_VAR(opline->result.var), value);
2604+
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
2605+
zend_assign_to_two_variables(EX_VAR(opline->result.var), variable_ptr, value, OP_DATA_TYPE, EX_USES_STRICT_TYPES());
2606+
} else {
2607+
zend_assign_to_variable(variable_ptr, value, OP_DATA_TYPE, EX_USES_STRICT_TYPES());
2608+
}
26052609
}
26062610
} else {
26072611
if (EXPECTED(Z_ISREF_P(object_ptr))) {
@@ -2695,12 +2699,14 @@ ZEND_VM_HANDLER(22, ZEND_ASSIGN, VAR|CV, CONST|TMP|VAR|CV, SPEC(RETVAL))
26952699
value = GET_OP2_ZVAL_PTR(BP_VAR_R);
26962700
variable_ptr = GET_OP1_ZVAL_PTR_PTR_UNDEF(BP_VAR_W);
26972701

2698-
value = zend_assign_to_variable(variable_ptr, value, OP2_TYPE, EX_USES_STRICT_TYPES());
26992702
if (RETURN_VALUE_USED(opline)) {
2700-
ZVAL_COPY(EX_VAR(opline->result.var), value);
2703+
zend_assign_to_two_variables(EX_VAR(opline->result.var), variable_ptr, value, OP2_TYPE, EX_USES_STRICT_TYPES());
2704+
} else {
2705+
zend_assign_to_variable(variable_ptr, value, OP2_TYPE, EX_USES_STRICT_TYPES());
27012706
}
2707+
27022708
FREE_OP1();
2703-
/* zend_assign_to_variable() always takes care of op2, never free it! */
2709+
/* zend_assign_to_(two_)variable(s)() always takes care of op2, never free it! */
27042710

27052711
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
27062712
}

0 commit comments

Comments
 (0)