diff --git a/NEWS b/NEWS index 7324e5c591aa..272247f676dc 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,10 @@ PHP NEWS . Fixed bug GH-22602 (gregoriantojd() and juliantojd() integer overflow with INT_MAX year). (arshidkv12) +- Core: + . Fixed use-after-free when a destructor bails out (memory_limit, timeout) + during zend_hash_clean(), e.g. from session_unset(). (iliaal) + - Date: . Update timelib to 2022.17. (Derick) . Fixed bug GH-19803 (Parsing a string with a single white space does create diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index 23637b94bceb..ae228529284b 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -1887,12 +1887,16 @@ ZEND_API void ZEND_FASTCALL zend_hash_clean(HashTable *ht) if (HT_HAS_STATIC_KEYS_ONLY(ht)) { if (HT_IS_WITHOUT_HOLES(ht)) { do { - ht->pDestructor(zv); + zval val = *zv; + ZVAL_UNDEF(zv); + ht->pDestructor(&val); } while (++zv != end); } else { do { if (EXPECTED(Z_TYPE_P(zv) != IS_UNDEF)) { - ht->pDestructor(zv); + zval val = *zv; + ZVAL_UNDEF(zv); + ht->pDestructor(&val); } } while (++zv != end); } @@ -1906,29 +1910,37 @@ ZEND_API void ZEND_FASTCALL zend_hash_clean(HashTable *ht) if (HT_HAS_STATIC_KEYS_ONLY(ht)) { if (HT_IS_WITHOUT_HOLES(ht)) { do { - ht->pDestructor(&p->val); + zval val = p->val; + ZVAL_UNDEF(&p->val); + ht->pDestructor(&val); } while (++p != end); } else { do { if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF)) { - ht->pDestructor(&p->val); + zval val = p->val; + ZVAL_UNDEF(&p->val); + ht->pDestructor(&val); } } while (++p != end); } } else if (HT_IS_WITHOUT_HOLES(ht)) { do { - ht->pDestructor(&p->val); + zval val = p->val; + ZVAL_UNDEF(&p->val); if (EXPECTED(p->key)) { zend_string_release(p->key); } + ht->pDestructor(&val); } while (++p != end); } else { do { if (EXPECTED(Z_TYPE(p->val) != IS_UNDEF)) { - ht->pDestructor(&p->val); + zval val = p->val; + ZVAL_UNDEF(&p->val); if (EXPECTED(p->key)) { zend_string_release(p->key); } + ht->pDestructor(&val); } } while (++p != end); } diff --git a/ext/session/tests/hash_clean_bailout.phpt b/ext/session/tests/hash_clean_bailout.phpt new file mode 100644 index 000000000000..cee52a4c99e3 --- /dev/null +++ b/ext/session/tests/hash_clean_bailout.phpt @@ -0,0 +1,31 @@ +--TEST-- +Use-after-free when a destructor bails out during zend_hash_clean() via session_unset() +--EXTENSIONS-- +session +--XLEAK-- +The intentional E_USER_ERROR bailout abandons the aborted request's op_array. +--SKIPIF-- + +--INI-- +session.save_handler=files +session.use_cookies=0 +session.cache_limiter= +--FILE-- + +--EXPECTF-- +Fatal error: boom in %s on line %d