diff --git a/NEWS b/NEWS index 4a8d8993fda..c63549f1237 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.3.0beta2 +- Streams: + . Fixed bug GH-11735 (Use-after-free when unregistering user stream wrapper + from itself). (ilutov) 20 Jul 2023, PHP 8.3.0beta1 diff --git a/Zend/tests/gh11735_1.phpt b/Zend/tests/gh11735_1.phpt new file mode 100644 index 00000000000..3ddf0185e98 --- /dev/null +++ b/Zend/tests/gh11735_1.phpt @@ -0,0 +1,16 @@ +--TEST-- +GH-11735: Use-after-free when unregistering user stream wrapper from user stream wrapper +--FILE-- + +--EXPECTF-- +resource(%d) of type (stream) diff --git a/Zend/tests/gh11735_2.phpt b/Zend/tests/gh11735_2.phpt new file mode 100644 index 00000000000..b568b6f6df3 --- /dev/null +++ b/Zend/tests/gh11735_2.phpt @@ -0,0 +1,17 @@ +--TEST-- +GH-11735: Use-after-free when unregistering user stream wrapper from user stream wrapper +--FILE-- + +--EXPECTF-- +Warning: fopen(foo://bar): Failed to open stream: "FooWrapper::stream_open" call failed in %s on line %d +bool(false) diff --git a/main/streams/userspace.c b/main/streams/userspace.c index c7e57ab3ba0..e769f2cdf22 100644 --- a/main/streams/userspace.c +++ b/main/streams/userspace.c @@ -315,6 +315,8 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char * us = emalloc(sizeof(*us)); us->wrapper = uwrap; + /* call_method_if_exists() may unregister the stream wrapper. Hold on to it. */ + GC_ADDREF(us->wrapper->resource); user_stream_create_object(uwrap, context, &us->object); if (Z_TYPE(us->object) == IS_UNDEF) { @@ -350,8 +352,6 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char * /* set wrapper data to be a reference to our object */ ZVAL_COPY(&stream->wrapperdata, &us->object); - - GC_ADDREF(us->wrapper->resource); } else { php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_OPEN "\" call failed", ZSTR_VAL(us->wrapper->ce->name)); @@ -361,6 +361,7 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char * if (stream == NULL) { zval_ptr_dtor(&us->object); ZVAL_UNDEF(&us->object); + zend_list_delete(us->wrapper->resource); efree(us); } zval_ptr_dtor(&zretval); @@ -403,6 +404,8 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char us = emalloc(sizeof(*us)); us->wrapper = uwrap; + /* call_method_if_exists() may unregister the stream wrapper. Hold on to it. */ + GC_ADDREF(us->wrapper->resource); user_stream_create_object(uwrap, context, &us->object); if (Z_TYPE(us->object) == IS_UNDEF) { @@ -425,8 +428,6 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char /* set wrapper data to be a reference to our object */ ZVAL_COPY(&stream->wrapperdata, &us->object); - - GC_ADDREF(us->wrapper->resource); } else { php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_DIR_OPEN "\" call failed", ZSTR_VAL(us->wrapper->ce->name)); @@ -436,6 +437,7 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char if (stream == NULL) { zval_ptr_dtor(&us->object); ZVAL_UNDEF(&us->object); + zend_list_delete(us->wrapper->resource); efree(us); } zval_ptr_dtor(&zretval);