From 91157cf5ae03c3423eeaf19d8167503c79359a38 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 5 Jun 2026 18:04:54 +0200 Subject: [PATCH] Fix stream_context_set_option() mutating the default context Since GH-20524, _php_stream_open_wrapper_ex() attaches the context to a stream that has none, including the implicitly substituted default context. Sharing the default context by reference let a later stream_context_set_option() on the stream mutate the global default context, leaking options into every other context-less stream. Only attach explicitly provided contexts. Stream errors already fall back to the default context when the stream has none, so error handling is unaffected. --- ...am_context_set_option_no_default_leak.phpt | 29 +++++++++++++++++++ main/streams/streams.c | 7 ++++- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 ext/standard/tests/streams/stream_context_set_option_no_default_leak.phpt diff --git a/ext/standard/tests/streams/stream_context_set_option_no_default_leak.phpt b/ext/standard/tests/streams/stream_context_set_option_no_default_leak.phpt new file mode 100644 index 000000000000..788b6d8a4676 --- /dev/null +++ b/ext/standard/tests/streams/stream_context_set_option_no_default_leak.phpt @@ -0,0 +1,29 @@ +--TEST-- +stream_context_set_option() on a context-less stream must not leak into the default context +--FILE-- + +--EXPECT-- +array(0) { +} +array(0) { +} +array(1) { + ["http"]=> + array(1) { + ["filename"]=> + string(8) "test.txt" + } +} diff --git a/main/streams/streams.c b/main/streams/streams.c index 715bbcfe0371..171748e6a08a 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -2248,7 +2248,12 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod stream->open_filename = __zend_orig_filename ? __zend_orig_filename : __zend_filename; stream->open_lineno = __zend_orig_lineno ? __zend_orig_lineno : __zend_lineno; #endif - if (stream->ctx == NULL && context != NULL && !persistent) { + /* Attach an explicitly provided context to the stream, but never the + * default context: sharing it by reference would let a later + * stream_context_set_option() on the stream mutate the global default + * context, leaking options into every other stream. Stream errors fall + * back to the default context on their own when the stream has none. */ + if (stream->ctx == NULL && context != NULL && context != FG(default_context) && !persistent) { php_stream_context_set(stream, context); } }