diff --git a/Zend/tests/gh19844.phpt b/Zend/tests/gh19844.phpt new file mode 100644 index 0000000000000..073e79d418a1d --- /dev/null +++ b/Zend/tests/gh19844.phpt @@ -0,0 +1,42 @@ +--TEST-- +GH-19844: Bail from stream_close() in zend_shutdown_executor_values() +--FILE-- + +--EXPECTF-- +Fatal error: Bail in %s on line %d + +Fatal error: Bail in %s on line %d + +Fatal error: Bail in %s on line %d diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index d4a373616fe92..5dd0e6f9ec5a2 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -274,9 +274,9 @@ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown) zval *zv; EG(flags) |= EG_FLAGS_IN_RESOURCE_SHUTDOWN; - zend_try { + // zend_try { zend_close_rsrc_list(&EG(regular_list)); - } zend_end_try(); + // } zend_end_try(); /* No PHP callback functions should be called after this point. */ EG(active) = 0; diff --git a/Zend/zend_list.c b/Zend/zend_list.c index bf599a2efca9b..3110ad78c50b3 100644 --- a/Zend/zend_list.c +++ b/Zend/zend_list.c @@ -217,12 +217,40 @@ void zend_close_rsrc_list(HashTable *ht) /* Reload ht->arData on each iteration, as it may be reallocated. */ uint32_t i = ht->nNumUsed; +#if 0 +retry: + zend_try { + while (i-- > 0) { + 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); + } + } + } + } zend_catch { + /* 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 + * zend_destroy_rsrc_list(&EG(regular_list)). At that point, the executor + * has already shut down and the process would crash. */ + goto retry; + } zend_end_try(); +#endif while (i-- > 0) { 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 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 + * zend_destroy_rsrc_list(&EG(regular_list)). At that point, the executor + * has already shut down and the process would crash. */ + zend_try { + zend_resource_dtor(res); + } zend_end_try(); } } }