From 6f1db1a00c3ff6660a0ad5fc6b9cf50f401fca01 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 19 May 2022 19:27:14 +0200 Subject: [PATCH] Fix stream_wrapper_unregister() resource leak Closes GH-8548 --- NEWS | 1 + Zend/tests/gh8548.phpt | 40 ++++++++++++++++++++++++++++++++++++++++ main/streams/userspace.c | 16 +++++++++++++++- 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/gh8548.phpt diff --git a/NEWS b/NEWS index 6b4dd24a1f221..c753c53622e4b 100644 --- a/NEWS +++ b/NEWS @@ -73,6 +73,7 @@ PHP NEWS - Streams: . Set IP_BIND_ADDRESS_NO_PORT if available when connecting to remote host. (Cristian Rodríguez) + . Fixed bug GH-8548 (stream_wrapper_unregister() leaks memory). (ilutov) - Zip: . add ZipArchive::clearError() method diff --git a/Zend/tests/gh8548.phpt b/Zend/tests/gh8548.phpt new file mode 100644 index 0000000000000..417844d9d82d7 --- /dev/null +++ b/Zend/tests/gh8548.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-8548: stream_wrapper_unregister() leaks memory +--FILE-- + +--EXPECT-- +bool(true) diff --git a/main/streams/userspace.c b/main/streams/userspace.c index 5a9ee4dc33290..8a8c1adff24d6 100644 --- a/main/streams/userspace.c +++ b/main/streams/userspace.c @@ -35,9 +35,10 @@ static int le_protocols; struct php_user_stream_wrapper { + php_stream_wrapper wrapper; char * protoname; zend_class_entry *ce; - php_stream_wrapper wrapper; + zend_resource *resource; }; static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *filename, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC); @@ -481,10 +482,12 @@ PHP_FUNCTION(stream_wrapper_register) uwrap->wrapper.wops = &user_stream_wops; uwrap->wrapper.abstract = uwrap; uwrap->wrapper.is_url = ((flags & PHP_STREAM_IS_URL) != 0); + uwrap->resource = NULL; rsrc = zend_register_resource(uwrap, le_protocols); if (php_register_url_stream_wrapper_volatile(protocol, &uwrap->wrapper) == SUCCESS) { + uwrap->resource = rsrc; RETURN_TRUE; } @@ -510,12 +513,23 @@ PHP_FUNCTION(stream_wrapper_unregister) RETURN_THROWS(); } + php_stream_wrapper *wrapper = zend_hash_find_ptr(php_stream_get_url_stream_wrappers_hash(), protocol); if (php_unregister_url_stream_wrapper_volatile(protocol) == FAILURE) { /* We failed */ php_error_docref(NULL, E_WARNING, "Unable to unregister protocol %s://", ZSTR_VAL(protocol)); RETURN_FALSE; } + ZEND_ASSERT(wrapper != NULL); + if (wrapper->wops == &user_stream_wops) { + struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper *)wrapper; + zend_resource *resource = uwrap->resource; + if (GC_DELREF(resource) == 0) { + // uwrap will be released by resource destructor + rc_dtor_func((zend_refcounted *)resource); + } + } + RETURN_TRUE; } /* }}} */