From: Nikita Popov Date: Tue, 4 Feb 2020 13:19:07 +0000 (+0100) Subject: Fixed bug #78598 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=220880ad2d54d10173a250637478da213b1ae8e2;p=php 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. --- diff --git a/NEWS b/NEWS index fd9c57ba95..7a0df9806a 100644 --- a/NEWS +++ b/NEWS @@ -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) diff --git a/Zend/tests/bug70662.phpt b/Zend/tests/bug70662.phpt index 2bda8141ba..ab540c9d16 100644 --- a/Zend/tests/bug70662.phpt +++ b/Zend/tests/bug70662.phpt @@ -14,5 +14,5 @@ var_dump($a); --EXPECT-- array(1) { ["b"]=> - int(1) + int(2) } diff --git a/Zend/tests/bug78598.phpt b/Zend/tests/bug78598.phpt new file mode 100644 index 0000000000..7e3559f27c --- /dev/null +++ b/Zend/tests/bug78598.phpt @@ -0,0 +1,31 @@ +--TEST-- +Bug #78598: Changing array during undef index RW error segfaults +--FILE-- + +--EXPECT-- +int(0) +int(0) +int(0) +int(0) diff --git a/Zend/tests/undef_index_to_exception.phpt b/Zend/tests/undef_index_to_exception.phpt new file mode 100644 index 0000000000..d113608a5e --- /dev/null +++ b/Zend/tests/undef_index_to_exception.phpt @@ -0,0 +1,46 @@ +--TEST-- +Converting undefined index/offset notice to exception +--FILE-- +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 diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 89e6178019..7b27c5a3a5 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -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;