diff --git a/Zend/zend_list.c b/Zend/zend_list.c index 5add19256a691..10aa9174cfccb 100644 --- a/Zend/zend_list.c +++ b/Zend/zend_list.c @@ -214,21 +214,34 @@ void zend_init_rsrc_plist(void) void zend_close_rsrc_list(HashTable *ht) { - /* Reload ht->arData on each iteration, as it may be reallocated. */ uint32_t i = ht->nNumUsed; + uint32_t num = ht->nNumUsed; retry: zend_try { while (i-- > 0) { + /* Reload ht->arData on each iteration, as it may be reallocated. */ zval *p = ZEND_HASH_ELEMENT(ht, i); if (Z_TYPE_P(p) != IS_UNDEF) { zend_resource *res = Z_PTR_P(p); if (res->type >= 0) { zend_resource_dtor(res); + + if (UNEXPECTED(ht->nNumUsed != num)) { + /* New resources were added, reloop from the start. + * We need to keep the top->down order to avoid freeing resources + * in use by the newly created resources. */ + i = num = ht->nNumUsed; + } } } } } zend_catch { + if (UNEXPECTED(ht->nNumUsed != num)) { + /* See above */ + i = num = ht->nNumUsed; + } + /* If we have bailed, we probably executed user code (e.g. user stream * API). Keep closing resources so they don't leak. User handlers must be * called now so they aren't called in zend_deactivate() on diff --git a/ext/standard/tests/streams/gh20286.phpt b/ext/standard/tests/streams/gh20286.phpt new file mode 100644 index 0000000000000..b4d340a4390f0 --- /dev/null +++ b/ext/standard/tests/streams/gh20286.phpt @@ -0,0 +1,43 @@ +--TEST-- +GH-20286 use after destroy on userland stream_close +--CREDITS-- +vi3tL0u1s +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: Cannot redeclare a() (previously declared in %s:%d) in %s on line %d + +Fatal error: Cannot redeclare a() (previously declared in %s on line %d + +Fatal error: Cannot redeclare a() (previously declared in %s on line %d + +Fatal error: Cannot redeclare a() (previously declared in %s on line %d diff --git a/main/streams/userspace.c b/main/streams/userspace.c index 8d15172ef1319..ba66d32465977 100644 --- a/main/streams/userspace.c +++ b/main/streams/userspace.c @@ -254,6 +254,7 @@ typedef struct _php_userstream_data php_userstream_data_t; static zend_result call_method_if_exists( zval *object, zval *method_name, zval *retval, uint32_t param_count, zval *params) { + ZEND_ASSERT(EG(active)); return zend_call_method_if_exists( Z_OBJ_P(object), Z_STR_P(method_name), retval, param_count, params); }