Skip to content

Commit 220880a

Browse files
committed
Fixed bug #78598
When performing an RW modification of an array offset, the undefined offset warning may call an error handler / OB callback, which may destroy the array we're supposed to change. Detect this by temporarily incrementing the reference count. If we find that the array has been modified/destroyed in the meantime, we do nothing -- the execution model here would be that the modification has happened on the destroyed version of the array.
1 parent 48a2471 commit 220880a

File tree

5 files changed

+129
-8
lines changed

5 files changed

+129
-8
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ PHP NEWS
1919
offset by reference). (Nikita)
2020
. Fixed bug #79792 (HT iterators not removed if empty array is destroyed).
2121
(Nikita)
22+
. Fixed bug #78598 (Changing array during undef index RW error segfaults).
23+
(Nikita)
2224

2325
- Fileinfo:
2426
. Fixed bug #79756 (finfo_file crash (FILEINFO_MIME)). (cmb)

Zend/tests/bug70662.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ var_dump($a);
1414
--EXPECT--
1515
array(1) {
1616
["b"]=>
17-
int(1)
17+
int(2)
1818
}

Zend/tests/bug78598.phpt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
Bug #78598: Changing array during undef index RW error segfaults
3+
--FILE--
4+
<?php
5+
6+
$my_var = null;
7+
set_error_handler(function() use(&$my_var) {
8+
$my_var = 0;
9+
});
10+
11+
$my_var[0] .= "xyz";
12+
var_dump($my_var);
13+
14+
$my_var = null;
15+
$my_var[0][0][0] .= "xyz";
16+
var_dump($my_var);
17+
18+
$my_var = null;
19+
$my_var["foo"] .= "xyz";
20+
var_dump($my_var);
21+
22+
$my_var = null;
23+
$my_var["foo"]["bar"]["baz"] .= "xyz";
24+
var_dump($my_var);
25+
26+
?>
27+
--EXPECT--
28+
int(0)
29+
int(0)
30+
int(0)
31+
int(0)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
--TEST--
2+
Converting undefined index/offset notice to exception
3+
--FILE--
4+
<?php
5+
6+
set_error_handler(function($_, $msg) {
7+
throw new Exception($msg);
8+
});
9+
10+
$test = [];
11+
try {
12+
$test[0] .= "xyz";
13+
} catch (Exception $e) {
14+
echo $e->getMessage(), "\n";
15+
}
16+
var_dump($test);
17+
18+
try {
19+
$test["key"] .= "xyz";
20+
} catch (Exception $e) {
21+
echo $e->getMessage(), "\n";
22+
}
23+
var_dump($test);
24+
25+
unset($test);
26+
try {
27+
$GLOBALS["test"] .= "xyz";
28+
} catch (Exception $e) {
29+
echo $e->getMessage(), "\n";
30+
}
31+
try {
32+
var_dump($test);
33+
} catch (Exception $e) {
34+
echo $e->getMessage(), "\n";
35+
}
36+
37+
?>
38+
--EXPECT--
39+
Undefined offset: 0
40+
array(0) {
41+
}
42+
Undefined index: key
43+
array(0) {
44+
}
45+
Undefined index: test
46+
Undefined variable: test

Zend/zend_execute.c

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1958,6 +1958,44 @@ static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_undefined_index(const
19581958
zend_error(E_NOTICE, "Undefined index: %s", ZSTR_VAL(offset));
19591959
}
19601960

1961+
static zend_never_inline ZEND_COLD int ZEND_FASTCALL zend_undefined_offset_write(
1962+
HashTable *ht, zend_long lval)
1963+
{
1964+
/* The array may be destroyed while throwing the notice.
1965+
* Temporarily increase the refcount to detect this situation. */
1966+
if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) {
1967+
GC_ADDREF(ht);
1968+
}
1969+
zend_undefined_offset(lval);
1970+
if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) {
1971+
zend_array_destroy(ht);
1972+
return FAILURE;
1973+
}
1974+
if (EG(exception)) {
1975+
return FAILURE;
1976+
}
1977+
return SUCCESS;
1978+
}
1979+
1980+
static zend_never_inline ZEND_COLD int ZEND_FASTCALL zend_undefined_index_write(
1981+
HashTable *ht, zend_string *offset)
1982+
{
1983+
/* The array may be destroyed while throwing the notice.
1984+
* Temporarily increase the refcount to detect this situation. */
1985+
if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) {
1986+
GC_ADDREF(ht);
1987+
}
1988+
zend_undefined_index(offset);
1989+
if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) {
1990+
zend_array_destroy(ht);
1991+
return FAILURE;
1992+
}
1993+
if (EG(exception)) {
1994+
return FAILURE;
1995+
}
1996+
return SUCCESS;
1997+
}
1998+
19611999
static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_undefined_method(const zend_class_entry *ce, const zend_string *method)
19622000
{
19632001
zend_throw_error(NULL, "Call to undefined method %s::%s()", ZSTR_VAL(ce->name), ZSTR_VAL(method));
@@ -2079,9 +2117,10 @@ static zend_always_inline zval *zend_fetch_dimension_address_inner(HashTable *ht
20792117
retval = &EG(uninitialized_zval);
20802118
break;
20812119
case BP_VAR_RW:
2082-
zend_undefined_offset(hval);
2083-
retval = zend_hash_index_update(ht, hval, &EG(uninitialized_zval));
2084-
break;
2120+
if (UNEXPECTED(zend_undefined_offset_write(ht, hval) == FAILURE)) {
2121+
return NULL;
2122+
}
2123+
/* break missing intentionally */
20852124
case BP_VAR_W:
20862125
retval = zend_hash_index_add_new(ht, hval, &EG(uninitialized_zval));
20872126
break;
@@ -2109,7 +2148,9 @@ static zend_always_inline zval *zend_fetch_dimension_address_inner(HashTable *ht
21092148
retval = &EG(uninitialized_zval);
21102149
break;
21112150
case BP_VAR_RW:
2112-
zend_undefined_index(offset_key);
2151+
if (UNEXPECTED(zend_undefined_index_write(ht, offset_key))) {
2152+
return NULL;
2153+
}
21132154
/* break missing intentionally */
21142155
case BP_VAR_W:
21152156
ZVAL_NULL(retval);
@@ -2127,9 +2168,10 @@ static zend_always_inline zval *zend_fetch_dimension_address_inner(HashTable *ht
21272168
retval = &EG(uninitialized_zval);
21282169
break;
21292170
case BP_VAR_RW:
2130-
zend_undefined_index(offset_key);
2131-
retval = zend_hash_update(ht, offset_key, &EG(uninitialized_zval));
2132-
break;
2171+
if (UNEXPECTED(zend_undefined_index_write(ht, offset_key) == FAILURE)) {
2172+
return NULL;
2173+
}
2174+
/* break missing intentionally */
21332175
case BP_VAR_W:
21342176
retval = zend_hash_add_new(ht, offset_key, &EG(uninitialized_zval));
21352177
break;

0 commit comments

Comments
 (0)