mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
Fix GH-14506: Closing a userspace stream inside a userspace handler causes heap corruption
Use the PHP_STREAM_FLAG_NO_FCLOSE flag to prevent closing a stream while a handler is running. We already do this in some other places as well. Only handlers that do something with the stream afterwards need changes. Closes GH-18797.
This commit is contained in:
4
NEWS
4
NEWS
@@ -40,6 +40,10 @@ PHP NEWS
|
||||
. Fix GH-19610 (Deprecation warnings in functions taking as argument).
|
||||
(Girgias)
|
||||
|
||||
- Streams:
|
||||
. Fixed bug GH-14506 (Closing a userspace stream inside a userspace handler
|
||||
causes heap corruption). (nielsdos)
|
||||
|
||||
- URI:
|
||||
. Fixed memory management of Uri\WhatWg\Url objects. (timwolla)
|
||||
. Fixed memory management of the internal "parse_url" URI parser.
|
||||
|
||||
95
ext/standard/tests/streams/gh14506.phpt
Normal file
95
ext/standard/tests/streams/gh14506.phpt
Normal file
@@ -0,0 +1,95 @@
|
||||
--TEST--
|
||||
GH-14506 (Closing a userspace stream inside a userspace handler causes heap corruption)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Bomb {
|
||||
|
||||
public $context;
|
||||
|
||||
function stream_open($path, $mode, $options, &$opened_path): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
function stream_write(string $data): int
|
||||
{
|
||||
global $readStream;
|
||||
fclose($readStream);
|
||||
return 0;
|
||||
}
|
||||
|
||||
function stream_read(int $count): false|string|null
|
||||
{
|
||||
global $readStream;
|
||||
fclose($readStream);
|
||||
return "";
|
||||
}
|
||||
|
||||
function stream_eof(): bool
|
||||
{
|
||||
global $readStream;
|
||||
fclose($readStream);
|
||||
return false;
|
||||
}
|
||||
|
||||
function stream_seek(int $offset, int $whence): bool
|
||||
{
|
||||
global $readStream;
|
||||
fclose($readStream);
|
||||
return false;
|
||||
}
|
||||
|
||||
function stream_cast(int $as)
|
||||
{
|
||||
global $readStream;
|
||||
fclose($readStream);
|
||||
return false;
|
||||
}
|
||||
|
||||
function stream_flush(): bool
|
||||
{
|
||||
global $readStream;
|
||||
fclose($readStream);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
stream_register_wrapper('bomb', Bomb::class);
|
||||
$readStream = fopen('bomb://1', 'r');
|
||||
fread($readStream, 1);
|
||||
fwrite($readStream, "x", 1);
|
||||
fseek($readStream, 0, SEEK_SET);
|
||||
$streams = [$readStream];
|
||||
$empty = [];
|
||||
try {
|
||||
stream_select($streams, $streams,$empty, 0);
|
||||
} catch (ValueError $e) {
|
||||
echo $e->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
|
||||
@@ -566,6 +566,9 @@ 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;
|
||||
|
||||
zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_WRITE, false);
|
||||
zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 1, args);
|
||||
zend_string_release_ex(func_name, false);
|
||||
@@ -575,6 +578,10 @@ static ssize_t php_userstreamop_write(php_stream *stream, const char *buf, size_
|
||||
php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_WRITE " is not implemented!",
|
||||
ZSTR_VAL(us->wrapper->ce->name));
|
||||
}
|
||||
|
||||
stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
|
||||
stream->flags |= orig_no_fclose;
|
||||
|
||||
/* Exception occurred */
|
||||
if (Z_ISUNDEF(retval)) {
|
||||
return -1;
|
||||
@@ -609,28 +616,31 @@ 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_LONG(&args[0], count);
|
||||
zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_READ, false);
|
||||
zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 1, args);
|
||||
zend_string_release_ex(func_name, false);
|
||||
|
||||
if (UNEXPECTED(Z_ISUNDEF(retval))) {
|
||||
return -1;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (UNEXPECTED(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);
|
||||
@@ -657,11 +667,11 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count
|
||||
"%s::" USERSTREAM_EOF " is not implemented! Assuming EOF",
|
||||
ZSTR_VAL(us->wrapper->ce->name));
|
||||
stream->eof = 1;
|
||||
return -1;
|
||||
goto err;
|
||||
}
|
||||
if (UNEXPECTED(Z_ISUNDEF(retval))) {
|
||||
stream->eof = 1;
|
||||
return -1;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (zval_is_true(&retval)) {
|
||||
@@ -669,7 +679,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)
|
||||
@@ -723,6 +741,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;
|
||||
|
||||
zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_SEEK, false);
|
||||
zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 2, args);
|
||||
zend_string_release_ex(func_name, false);
|
||||
@@ -737,7 +758,8 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when
|
||||
|
||||
zval_ptr_dtor(&retval);
|
||||
|
||||
return -1;
|
||||
ret = -1;
|
||||
goto out;
|
||||
} else if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF && zval_is_true(&retval)) {
|
||||
ret = 0;
|
||||
} else {
|
||||
@@ -748,7 +770,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 */
|
||||
@@ -767,6 +789,11 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when
|
||||
}
|
||||
|
||||
zval_ptr_dtor(&retval);
|
||||
|
||||
out:
|
||||
stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
|
||||
stream->flags |= orig_no_fclose;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1394,6 +1421,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;
|
||||
|
||||
zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_CAST, false);
|
||||
zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 1, args);
|
||||
zend_string_release_ex(func_name, false);
|
||||
@@ -1403,7 +1433,7 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr)
|
||||
php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " is not implemented!",
|
||||
ZSTR_VAL(us->wrapper->ce->name));
|
||||
}
|
||||
return FAILURE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
do {
|
||||
@@ -1432,6 +1462,10 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr)
|
||||
|
||||
zval_ptr_dtor(&retval);
|
||||
|
||||
out:
|
||||
stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
|
||||
stream->flags |= orig_no_fclose;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user