Fix stream_wrapper_unregister() resource leak

Closes GH-8548
Closes GH-8587
This commit is contained in:
Ilija Tovilo 2022-05-19 19:27:14 +02:00
parent 82ab848daf
commit a5a89cc222
No known key found for this signature in database
GPG key ID: A4F5D403F118200A
3 changed files with 53 additions and 1 deletions

1
NEWS
View file

@ -73,6 +73,7 @@ PHP NEWS
- Streams: - Streams:
. Set IP_BIND_ADDRESS_NO_PORT if available when connecting to remote host. . Set IP_BIND_ADDRESS_NO_PORT if available when connecting to remote host.
(Cristian Rodríguez) (Cristian Rodríguez)
. Fixed bug GH-8548 (stream_wrapper_unregister() leaks memory). (ilutov)
- Zip: - Zip:
. add ZipArchive::clearError() method . add ZipArchive::clearError() method

40
Zend/tests/gh8548.phpt Normal file
View file

@ -0,0 +1,40 @@
--TEST--
GH-8548: stream_wrapper_unregister() leaks memory
--FILE--
<?php
class Wrapper
{
public $context;
public function stream_open(string $path, string $mode, int $options): bool
{
return true;
}
}
function test() {
if (!stream_wrapper_register('foo', \Wrapper::class)) {
throw new \Exception('Could not register stream wrapper');
}
if (!stream_wrapper_unregister('foo')) {
throw new \Exception('Could not unregister stream wrapper');
}
}
// The first iterations will allocate space for things like the resource list
for ($i = 0; $i < 5; $i++) {
test();
}
$before = memory_get_usage();
for ($i = 0; $i < 1000; $i++) {
test();
}
$after = memory_get_usage();
var_dump($before === $after);
?>
--EXPECT--
bool(true)

View file

@ -35,9 +35,10 @@
static int le_protocols; static int le_protocols;
struct php_user_stream_wrapper { struct php_user_stream_wrapper {
php_stream_wrapper wrapper;
char * protoname; char * protoname;
zend_class_entry *ce; 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); 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.wops = &user_stream_wops;
uwrap->wrapper.abstract = uwrap; uwrap->wrapper.abstract = uwrap;
uwrap->wrapper.is_url = ((flags & PHP_STREAM_IS_URL) != 0); uwrap->wrapper.is_url = ((flags & PHP_STREAM_IS_URL) != 0);
uwrap->resource = NULL;
rsrc = zend_register_resource(uwrap, le_protocols); rsrc = zend_register_resource(uwrap, le_protocols);
if (php_register_url_stream_wrapper_volatile(protocol, &uwrap->wrapper) == SUCCESS) { if (php_register_url_stream_wrapper_volatile(protocol, &uwrap->wrapper) == SUCCESS) {
uwrap->resource = rsrc;
RETURN_TRUE; RETURN_TRUE;
} }
@ -510,12 +513,20 @@ PHP_FUNCTION(stream_wrapper_unregister)
RETURN_THROWS(); 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) { if (php_unregister_url_stream_wrapper_volatile(protocol) == FAILURE) {
/* We failed */ /* We failed */
php_error_docref(NULL, E_WARNING, "Unable to unregister protocol %s://", ZSTR_VAL(protocol)); php_error_docref(NULL, E_WARNING, "Unable to unregister protocol %s://", ZSTR_VAL(protocol));
RETURN_FALSE; RETURN_FALSE;
} }
ZEND_ASSERT(wrapper != NULL);
if (wrapper->wops == &user_stream_wops) {
struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper *)wrapper;
// uwrap will be released by resource destructor
zend_list_delete(uwrap->resource);
}
RETURN_TRUE; RETURN_TRUE;
} }
/* }}} */ /* }}} */