Skip to content

Commit 71ddede

Browse files
nielsdosiluuu1994
authored andcommitted
Fix GH-10168: heap-buffer-overflow at zval_undefined_cv
The problem is that we're using the variable_ptr in the opcode handler *after* it has already been destroyed. The solution is to create a specialised version of zend_assign_to_variable which takes in two destination zval pointers. Closes GH-10524
1 parent e6281db commit 71ddede

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
@@ -10,6 +10,8 @@ PHP NEWS
1010
Generator emits an unavoidable fatal error or crashes). (Arnaud)
1111
. Fixed bug GH-10437 (Segfault/assertion when using fibers in shutdown
1212
function after bailout). (trowski)
13+
. Fixed bug GH-10168: use-after-free when utilizing assigned object freed
14+
during assignment. (nielsdos)
1315

1416
- Date:
1517
. 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
@@ -3562,7 +3562,7 @@ static zend_always_inline void i_zval_ptr_dtor_noref(zval *zval_ptr) {
35623562
}
35633563
}
35643564

3565-
ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *orig_value, zend_uchar value_type, bool strict)
3565+
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)
35663566
{
35673567
bool ret;
35683568
zval value;
@@ -3582,6 +3582,9 @@ ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *orig_value, ze
35823582
} else {
35833583
zval_ptr_dtor_nogc(&value);
35843584
}
3585+
if (result_variable_ptr) {
3586+
ZVAL_COPY(result_variable_ptr, variable_ptr);
3587+
}
35853588
if (value_type & (IS_VAR|IS_TMP_VAR)) {
35863589
if (UNEXPECTED(ref)) {
35873590
if (UNEXPECTED(GC_DELREF(ref) == 0)) {
@@ -3595,6 +3598,11 @@ ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *orig_value, ze
35953598
return variable_ptr;
35963599
}
35973600

3601+
ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *orig_value, zend_uchar value_type, bool strict)
3602+
{
3603+
return zend_assign_to_typed_ref_and_result(variable_ptr, orig_value, value_type, strict, NULL);
3604+
}
3605+
35983606
ZEND_API bool ZEND_FASTCALL zend_verify_prop_assignable_by_ref(zend_property_info *prop_info, zval *orig_val, bool strict) {
35993607
zval *val = orig_val;
36003608
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
@@ -2577,6 +2577,9 @@ ZEND_VM_C_LABEL(try_assign_dim_array):
25772577
Z_ADDREF_P(value);
25782578
}
25792579
}
2580+
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
2581+
ZVAL_COPY(EX_VAR(opline->result.var), value);
2582+
}
25802583
} else {
25812584
dim = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);
25822585
if (OP2_TYPE == IS_CONST) {
@@ -2588,10 +2591,11 @@ ZEND_VM_C_LABEL(try_assign_dim_array):
25882591
ZEND_VM_C_GOTO(assign_dim_error);
25892592
}
25902593
value = GET_OP_DATA_ZVAL_PTR(BP_VAR_R);
2591-
value = zend_assign_to_variable(variable_ptr, value, OP_DATA_TYPE, EX_USES_STRICT_TYPES());
2592-
}
2593-
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
2594-
ZVAL_COPY(EX_VAR(opline->result.var), value);
2594+
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
2595+
zend_assign_to_two_variables(EX_VAR(opline->result.var), variable_ptr, value, OP_DATA_TYPE, EX_USES_STRICT_TYPES());
2596+
} else {
2597+
zend_assign_to_variable(variable_ptr, value, OP_DATA_TYPE, EX_USES_STRICT_TYPES());
2598+
}
25952599
}
25962600
} else {
25972601
if (EXPECTED(Z_ISREF_P(object_ptr))) {
@@ -2685,12 +2689,14 @@ ZEND_VM_HANDLER(22, ZEND_ASSIGN, VAR|CV, CONST|TMP|VAR|CV, SPEC(RETVAL))
26852689
value = GET_OP2_ZVAL_PTR(BP_VAR_R);
26862690
variable_ptr = GET_OP1_ZVAL_PTR_PTR_UNDEF(BP_VAR_W);
26872691

2688-
value = zend_assign_to_variable(variable_ptr, value, OP2_TYPE, EX_USES_STRICT_TYPES());
26892692
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
2690-
ZVAL_COPY(EX_VAR(opline->result.var), value);
2693+
zend_assign_to_two_variables(EX_VAR(opline->result.var), variable_ptr, value, OP2_TYPE, EX_USES_STRICT_TYPES());
2694+
} else {
2695+
zend_assign_to_variable(variable_ptr, value, OP2_TYPE, EX_USES_STRICT_TYPES());
26912696
}
2697+
26922698
FREE_OP1_VAR_PTR();
2693-
/* zend_assign_to_variable() always takes care of op2, never free it! */
2699+
/* zend_assign_to_(two_)variable(s)() always takes care of op2, never free it! */
26942700

26952701
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
26962702
}

0 commit comments

Comments
 (0)