Commit 631bf8acdf4 for php.net
commit 631bf8acdf4b0e108da1718a594e77c9bcc8e88a
Author: David CARLIER <devnexen@gmail.com>
Date: Thu Jun 4 17:25:40 2026 +0100
Fix GH-22200: stream api memory leaks. (#22204)
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.
diff --git a/ext/standard/tests/streams/gh22200.phpt b/ext/standard/tests/streams/gh22200.phpt
new file mode 100644
index 00000000000..01a58bbf8cf
--- /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--
+<?php
+
+// When the warning emitted for the failing outer stream operation is reported,
+// php_stream_error_operation_end() has already popped the operation off the
+// stack. The user error handler then runs another stream operation, which
+// reuses the same operation pool slot and orphans the in-flight error entry,
+// leaking it.
+set_error_handler(function ($errno, $errstr) {
+ @file_get_contents(__DIR__ . '/gh22200-does-not-exist-inner');
+ return true;
+});
+
+file_get_contents(__DIR__ . '/gh22200-does-not-exist-outer');
+
+echo "done\n";
+
+?>
+--EXPECT--
+done
diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c
index c4a2f74db8a..3d7056c44d1 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;