Skip to content

Commit 972a5a0

Browse files
committed
Merge branch 'PHP-8.1' into PHP-8.2
* PHP-8.1: Fix GH-10168: heap-buffer-overflow at zval_undefined_cv
2 parents ffdd75a + 71ddede commit 972a5a0

File tree

8 files changed

+486
-193
lines changed

8 files changed

+486
-193
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ PHP NEWS
1111
Generator emits an unavoidable fatal error or crashes). (Arnaud)
1212
. Fixed bug GH-10437 (Segfault/assertion when using fibers in shutdown
1313
function after bailout). (trowski)
14+
. Fixed bug GH-10168: use-after-free when utilizing assigned object freed
15+
during assignment. (nielsdos)
1416

1517
- Date:
1618
. Fix GH-10447 ('p' format specifier does not yield 'Z' for 00:00). (Derick)

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
@@ -3525,7 +3525,7 @@ static zend_always_inline void i_zval_ptr_dtor_noref(zval *zval_ptr) {
35253525
}
35263526
}
35273527

3528-
ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *orig_value, zend_uchar value_type, bool strict)
3528+
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)
35293529
{
35303530
bool ret;
35313531
zval value;
@@ -3545,6 +3545,9 @@ ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *orig_value, ze
35453545
} else {
35463546
zval_ptr_dtor_nogc(&value);
35473547
}
3548+
if (result_variable_ptr) {
3549+
ZVAL_COPY(result_variable_ptr, variable_ptr);
3550+
}
35483551
if (value_type & (IS_VAR|IS_TMP_VAR)) {
35493552
if (UNEXPECTED(ref)) {
35503553
if (UNEXPECTED(GC_DELREF(ref) == 0)) {
@@ -3558,6 +3561,11 @@ ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *orig_value, ze
35583561
return variable_ptr;
35593562
}
35603563

3564+
ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *orig_value, zend_uchar value_type, bool strict)
3565+
{
3566+
return zend_assign_to_typed_ref_and_result(variable_ptr, orig_value, value_type, strict, NULL);
3567+
}
3568+
35613569
ZEND_API bool ZEND_FASTCALL zend_verify_prop_assignable_by_ref(zend_property_info *prop_info, zval *orig_val, bool strict) {
35623570
zval *val = orig_val;
35633571
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
@@ -108,6 +108,7 @@ ZEND_API bool zend_verify_internal_return_type(zend_function *zf, zval *ret);
108108
ZEND_API void ZEND_FASTCALL zend_ref_add_type_source(zend_property_info_source_list *source_list, zend_property_info *prop);
109109
ZEND_API void ZEND_FASTCALL zend_ref_del_type_source(zend_property_info_source_list *source_list, zend_property_info *prop);
110110

111+
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);
111112
ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *value, zend_uchar value_type, bool strict);
112113

113114
static zend_always_inline void zend_copy_to_variable(zval *variable_ptr, zval *value, zend_uchar value_type)
@@ -137,12 +138,22 @@ static zend_always_inline void zend_copy_to_variable(zval *variable_ptr, zval *v
137138
}
138139
}
139140

141+
static zend_always_inline void zend_handle_garbage_from_variable_assignment(zend_refcounted *garbage)
142+
{
143+
if (GC_DELREF(garbage) == 0) {
144+
rc_dtor_func(garbage);
145+
} else { /* we need to split */
146+
/* optimized version of GC_ZVAL_CHECK_POSSIBLE_ROOT(variable_ptr) */
147+
if (UNEXPECTED(GC_MAY_LEAK(garbage))) {
148+
gc_possible_root(garbage);
149+
}
150+
}
151+
}
152+
140153
static zend_always_inline zval* zend_assign_to_variable(zval *variable_ptr, zval *value, zend_uchar value_type, bool strict)
141154
{
142155
do {
143156
if (UNEXPECTED(Z_REFCOUNTED_P(variable_ptr))) {
144-
zend_refcounted *garbage;
145-
146157
if (Z_ISREF_P(variable_ptr)) {
147158
if (UNEXPECTED(ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(variable_ptr)))) {
148159
return zend_assign_to_typed_ref(variable_ptr, value, value_type, strict);
@@ -153,21 +164,42 @@ static zend_always_inline zval* zend_assign_to_variable(zval *variable_ptr, zval
153164
break;
154165
}
155166
}
156-
garbage = Z_COUNTED_P(variable_ptr);
167+
zend_refcounted *garbage = Z_COUNTED_P(variable_ptr);
157168
zend_copy_to_variable(variable_ptr, value, value_type);
158-
if (GC_DELREF(garbage) == 0) {
159-
rc_dtor_func(garbage);
160-
} else { /* we need to split */
161-
/* optimized version of GC_ZVAL_CHECK_POSSIBLE_ROOT(variable_ptr) */
162-
if (UNEXPECTED(GC_MAY_LEAK(garbage))) {
163-
gc_possible_root(garbage);
169+
zend_handle_garbage_from_variable_assignment(garbage);
170+
return variable_ptr;
171+
}
172+
} while (0);
173+
174+
zend_copy_to_variable(variable_ptr, value, value_type);
175+
return variable_ptr;
176+
}
177+
178+
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)
179+
{
180+
do {
181+
if (UNEXPECTED(Z_REFCOUNTED_P(variable_ptr))) {
182+
if (Z_ISREF_P(variable_ptr)) {
183+
if (UNEXPECTED(ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(variable_ptr)))) {
184+
variable_ptr = zend_assign_to_typed_ref_and_result(variable_ptr, value, value_type, strict, result_variable_ptr);
185+
return variable_ptr;
186+
}
187+
188+
variable_ptr = Z_REFVAL_P(variable_ptr);
189+
if (EXPECTED(!Z_REFCOUNTED_P(variable_ptr))) {
190+
break;
164191
}
165192
}
193+
zend_refcounted *garbage = Z_COUNTED_P(variable_ptr);
194+
zend_copy_to_variable(variable_ptr, value, value_type);
195+
ZVAL_COPY(result_variable_ptr, variable_ptr);
196+
zend_handle_garbage_from_variable_assignment(garbage);
166197
return variable_ptr;
167198
}
168199
} while (0);
169200

170201
zend_copy_to_variable(variable_ptr, value, value_type);
202+
ZVAL_COPY(result_variable_ptr, variable_ptr);
171203
return variable_ptr;
172204
}
173205

Zend/zend_vm_def.h

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2585,6 +2585,9 @@ ZEND_VM_C_LABEL(try_assign_dim_array):
25852585
Z_ADDREF_P(value);
25862586
}
25872587
}
2588+
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
2589+
ZVAL_COPY(EX_VAR(opline->result.var), value);
2590+
}
25882591
} else {
25892592
dim = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);
25902593
if (OP2_TYPE == IS_CONST) {
@@ -2596,10 +2599,11 @@ ZEND_VM_C_LABEL(try_assign_dim_array):
25962599
ZEND_VM_C_GOTO(assign_dim_error);
25972600
}
25982601
value = GET_OP_DATA_ZVAL_PTR(BP_VAR_R);
2599-
value = zend_assign_to_variable(variable_ptr, value, OP_DATA_TYPE, EX_USES_STRICT_TYPES());
2600-
}
2601-
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
2602-
ZVAL_COPY(EX_VAR(opline->result.var), value);
2602+
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
2603+
zend_assign_to_two_variables(EX_VAR(opline->result.var), variable_ptr, value, OP_DATA_TYPE, EX_USES_STRICT_TYPES());
2604+
} else {
2605+
zend_assign_to_variable(variable_ptr, value, OP_DATA_TYPE, EX_USES_STRICT_TYPES());
2606+
}
26032607
}
26042608
} else {
26052609
if (EXPECTED(Z_ISREF_P(object_ptr))) {
@@ -2693,12 +2697,14 @@ ZEND_VM_HANDLER(22, ZEND_ASSIGN, VAR|CV, CONST|TMP|VAR|CV, SPEC(RETVAL))
26932697
value = GET_OP2_ZVAL_PTR(BP_VAR_R);
26942698
variable_ptr = GET_OP1_ZVAL_PTR_PTR_UNDEF(BP_VAR_W);
26952699

2696-
value = zend_assign_to_variable(variable_ptr, value, OP2_TYPE, EX_USES_STRICT_TYPES());
26972700
if (RETURN_VALUE_USED(opline)) {
2698-
ZVAL_COPY(EX_VAR(opline->result.var), value);
2701+
zend_assign_to_two_variables(EX_VAR(opline->result.var), variable_ptr, value, OP2_TYPE, EX_USES_STRICT_TYPES());
2702+
} else {
2703+
zend_assign_to_variable(variable_ptr, value, OP2_TYPE, EX_USES_STRICT_TYPES());
26992704
}
2705+
27002706
FREE_OP1();
2701-
/* zend_assign_to_variable() always takes care of op2, never free it! */
2707+
/* zend_assign_to_(two_)variable(s)() always takes care of op2, never free it! */
27022708

27032709
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
27042710
}

0 commit comments

Comments
 (0)