From 188ed37675f02e892c13e44549ee067372328742 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Mon, 1 Jun 2026 20:41:58 +0100 Subject: [PATCH 1/2] Fix GH-22200: stream api memory leaks. adding reproducer. Fix #22200 --- ext/standard/tests/streams/gh22200.phpt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 ext/standard/tests/streams/gh22200.phpt diff --git a/ext/standard/tests/streams/gh22200.phpt b/ext/standard/tests/streams/gh22200.phpt new file mode 100644 index 000000000000..01a58bbf8cf2 --- /dev/null +++ b/ext/standard/tests/streams/gh22200.phpt @@ -0,0 +1,22 @@ +--TEST-- +GH-22200 (Memory leaks in the stream errors code on reentrant error handler) +--FILE-- + +--EXPECT-- +done From 4f1cac0c0ce1a9991f58bff4160a38e38a4e9947 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Mon, 1 Jun 2026 21:34:43 +0100 Subject: [PATCH 2/2] Fix GH-22200: stream api memory leaks. Report the stream errors before popping the operation off the stack, so a reentrant error handler that runs another stream operation no longer reuses the in-flight operation pool slot and orphans its error entry. While at it, free the docref in php_stream_error_entry_free(), matching the legacy list destructor. --- main/streams/stream_errors.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index c4a2f74db8a9..3d7056c44d1e 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -215,6 +215,7 @@ static void php_stream_error_entry_free(php_stream_error_entry *entry) zend_string_release(entry->message); efree(entry->wrapper_name); efree(entry->param); + efree(entry->docref); efree(entry); entry = next; } @@ -449,9 +450,6 @@ PHPAPI void php_stream_error_operation_end(php_stream_context *context) return; } - state->operation_depth--; - state->current_operation = php_stream_get_parent_operation(); - if (op->error_count > 0) { if (context == NULL) { context = FG(default_context); @@ -527,6 +525,9 @@ PHPAPI void php_stream_error_operation_end(php_stream_context *context) } } + state->operation_depth--; + state->current_operation = php_stream_get_parent_operation(); + op->first_error = NULL; op->last_error = NULL; op->error_count = 0;