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.
This commit is contained in:
Nikita Popov 2020-02-04 14:19:07 +01:00
parent 48a247178e
commit 220880ad2d
5 changed files with 129 additions and 8 deletions

2
NEWS
View File

@ -19,6 +19,8 @@ PHP NEWS
offset by reference). (Nikita)
. Fixed bug #79792 (HT iterators not removed if empty array is destroyed).
(Nikita)
. Fixed bug #78598 (Changing array during undef index RW error segfaults).
(Nikita)
- Fileinfo:
. Fixed bug #79756 (finfo_file crash (FILEINFO_MIME)). (cmb)

View File

@ -14,5 +14,5 @@ var_dump($a);
--EXPECT--
array(1) {
["b"]=>
int(1)
int(2)
}

31
Zend/tests/bug78598.phpt Normal file
View File

@ -0,0 +1,31 @@
--TEST--
Bug #78598: Changing array during undef index RW error segfaults
--FILE--
<?php
$my_var = null;
set_error_handler(function() use(&$my_var) {
$my_var = 0;
});
$my_var[0] .= "xyz";
var_dump($my_var);
$my_var = null;
$my_var[0][0][0] .= "xyz";
var_dump($my_var);
$my_var = null;
$my_var["foo"] .= "xyz";
var_dump($my_var);
$my_var = null;
$my_var["foo"]["bar"]["baz"] .= "xyz";
var_dump($my_var);
?>
--EXPECT--
int(0)
int(0)
int(0)
int(0)

View File

@ -0,0 +1,46 @@
--TEST--
Converting undefined index/offset notice to exception
--FILE--
<?php
set_error_handler(function($_, $msg) {
throw new Exception($msg);
});
$test = [];
try {
$test[0] .= "xyz";
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
var_dump($test);
try {
$test["key"] .= "xyz";
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
var_dump($test);
unset($test);
try {
$GLOBALS["test"] .= "xyz";
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
try {
var_dump($test);
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
Undefined offset: 0
array(0) {
}
Undefined index: key
array(0) {
}
Undefined index: test
Undefined variable: test

View File

@ -1958,6 +1958,44 @@ static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_undefined_index(const
zend_error(E_NOTICE, "Undefined index: %s", ZSTR_VAL(offset));
}
static zend_never_inline ZEND_COLD int ZEND_FASTCALL zend_undefined_offset_write(
HashTable *ht, zend_long lval)
{
/* The array may be destroyed while throwing the notice.
* Temporarily increase the refcount to detect this situation. */
if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) {
GC_ADDREF(ht);
}
zend_undefined_offset(lval);
if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) {
zend_array_destroy(ht);
return FAILURE;
}
if (EG(exception)) {
return FAILURE;
}
return SUCCESS;
}
static zend_never_inline ZEND_COLD int ZEND_FASTCALL zend_undefined_index_write(
HashTable *ht, zend_string *offset)
{
/* The array may be destroyed while throwing the notice.
* Temporarily increase the refcount to detect this situation. */
if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) {
GC_ADDREF(ht);
}
zend_undefined_index(offset);
if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) {
zend_array_destroy(ht);
return FAILURE;
}
if (EG(exception)) {
return FAILURE;
}
return SUCCESS;
}
static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_undefined_method(const zend_class_entry *ce, const zend_string *method)
{
zend_throw_error(NULL, "Call to undefined method %s::%s()", ZSTR_VAL(ce->name), ZSTR_VAL(method));
@ -2079,9 +2117,10 @@ num_undef:
retval = &EG(uninitialized_zval);
break;
case BP_VAR_RW:
zend_undefined_offset(hval);
retval = zend_hash_index_update(ht, hval, &EG(uninitialized_zval));
break;
if (UNEXPECTED(zend_undefined_offset_write(ht, hval) == FAILURE)) {
return NULL;
}
/* break missing intentionally */
case BP_VAR_W:
retval = zend_hash_index_add_new(ht, hval, &EG(uninitialized_zval));
break;
@ -2109,7 +2148,9 @@ str_index:
retval = &EG(uninitialized_zval);
break;
case BP_VAR_RW:
zend_undefined_index(offset_key);
if (UNEXPECTED(zend_undefined_index_write(ht, offset_key))) {
return NULL;
}
/* break missing intentionally */
case BP_VAR_W:
ZVAL_NULL(retval);
@ -2127,9 +2168,10 @@ str_index:
retval = &EG(uninitialized_zval);
break;
case BP_VAR_RW:
zend_undefined_index(offset_key);
retval = zend_hash_update(ht, offset_key, &EG(uninitialized_zval));
break;
if (UNEXPECTED(zend_undefined_index_write(ht, offset_key) == FAILURE)) {
return NULL;
}
/* break missing intentionally */
case BP_VAR_W:
retval = zend_hash_add_new(ht, offset_key, &EG(uninitialized_zval));
break;