diff --git a/ext/standard/tests/streams/gh14506.phpt b/ext/standard/tests/streams/gh14506.phpt new file mode 100644 index 0000000000000..f83eba4f1ff30 --- /dev/null +++ b/ext/standard/tests/streams/gh14506.phpt @@ -0,0 +1,95 @@ +--TEST-- +GH-14506 (Closing a userspace stream inside a userspace handler causes heap corruption) +--FILE-- +getMessage(), "\n"; +} +fflush($readStream); +try { + fclose($readStream); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +Warning: fclose(): cannot close the provided stream, as it must not be manually closed in %s on line %d + +Warning: fclose(): cannot close the provided stream, as it must not be manually closed in %s on line %d + +Warning: fclose(): cannot close the provided stream, as it must not be manually closed in %s on line %d + +Warning: fclose(): cannot close the provided stream, as it must not be manually closed in %s on line %d + +Warning: fclose(): cannot close the provided stream, as it must not be manually closed in %s on line %d + +Warning: stream_select(): Cannot represent a stream of type user-space as a select()able descriptor in %s on line %d + +Warning: fclose(): cannot close the provided stream, as it must not be manually closed in %s on line %d + +Warning: stream_select(): Cannot represent a stream of type user-space as a select()able descriptor in %s on line %d +No stream arrays were passed +fclose(): Argument #1 ($stream) must be an open stream resource diff --git a/main/streams/userspace.c b/main/streams/userspace.c index 0bb80acf6b7c2..25ee05fb0f8b3 100644 --- a/main/streams/userspace.c +++ b/main/streams/userspace.c @@ -575,10 +575,16 @@ static ssize_t php_userstreamop_write(php_stream *stream, const char *buf, size_ ZVAL_STRINGL(&args[0], (char*)buf, count); + uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE; + stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE; + call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args); zval_ptr_dtor(&args[0]); zval_ptr_dtor(&func_name); + stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE; + stream->flags |= orig_no_fclose; + if (EG(exception)) { return -1; } @@ -620,6 +626,9 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count assert(us != NULL); + uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE; + stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE; + ZVAL_STRINGL(&func_name, USERSTREAM_READ, sizeof(USERSTREAM_READ)-1); ZVAL_LONG(&args[0], count); @@ -630,22 +639,22 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count zval_ptr_dtor(&func_name); if (EG(exception)) { - return -1; + goto err; } if (call_result == FAILURE) { php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_READ " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); - return -1; + goto err; } if (Z_TYPE(retval) == IS_FALSE) { - return -1; + goto err; } if (!try_convert_to_string(&retval)) { zval_ptr_dtor(&retval); - return -1; + goto err; } didread = Z_STRLEN(retval); @@ -669,7 +678,7 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count if (EG(exception)) { stream->eof = 1; - return -1; + goto err; } if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF && zval_is_true(&retval)) { @@ -684,7 +693,15 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count zval_ptr_dtor(&retval); + stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE; + stream->flags |= orig_no_fclose; + return didread; + +err: + stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE; + stream->flags |= orig_no_fclose; + return -1; } static int php_userstreamop_close(php_stream *stream, int close_handle) @@ -749,6 +766,9 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when ZVAL_LONG(&args[0], offset); ZVAL_LONG(&args[1], whence); + uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE; + stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE; + call_result = call_method_if_exists(&us->object, &func_name, &retval, 2, args); zval_ptr_dtor(&args[0]); @@ -773,7 +793,7 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when ZVAL_UNDEF(&retval); if (ret) { - return ret; + goto out; } /* now determine where we are */ @@ -793,6 +813,11 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when zval_ptr_dtor(&retval); zval_ptr_dtor(&func_name); + +out: + stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE; + stream->flags |= orig_no_fclose; + return ret; } @@ -1403,6 +1428,9 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr) break; } + uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE; + stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE; + call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args); do { @@ -1439,6 +1467,9 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr) zval_ptr_dtor(&func_name); zval_ptr_dtor(&args[0]); + stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE; + stream->flags |= orig_no_fclose; + return ret; }