Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 4b99519

Browse files
committed
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.
1 parent 066553c commit 4b99519

File tree

3 files changed

+142
-9
lines changed

3 files changed

+142
-9
lines changed

‎NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ PHP NEWS
4040
. Fix GH-19610 (Deprecation warnings in functions taking as argument).
4141
(Girgias)
4242

43+
- Streams:
44+
. Fixed bug GH-14506 (Closing a userspace stream inside a userspace handler
45+
causes heap corruption). (nielsdos)
46+
4347
- URI:
4448
. Fixed memory management of Uri\WhatWg\Url objects. (timwolla)
4549
. Fixed memory management of the internal "parse_url" URI parser.

‎ext/standard/tests/streams/gh14506.phpt

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
--TEST--
2+
GH-14506 (Closing a userspace stream inside a userspace handler causes heap corruption)
3+
--FILE--
4+
<?php
5+
6+
class Bomb {
7+
8+
public $context;
9+
10+
function stream_open($path, $mode, $options, &$opened_path): bool
11+
{
12+
return true;
13+
}
14+
15+
function stream_write(string $data): int
16+
{
17+
global $readStream;
18+
fclose($readStream);
19+
return 0;
20+
}
21+
22+
function stream_read(int $count): false|string|null
23+
{
24+
global $readStream;
25+
fclose($readStream);
26+
return "";
27+
}
28+
29+
function stream_eof(): bool
30+
{
31+
global $readStream;
32+
fclose($readStream);
33+
return false;
34+
}
35+
36+
function stream_seek(int $offset, int $whence): bool
37+
{
38+
global $readStream;
39+
fclose($readStream);
40+
return false;
41+
}
42+
43+
function stream_cast(int $as)
44+
{
45+
global $readStream;
46+
fclose($readStream);
47+
return false;
48+
}
49+
50+
function stream_flush(): bool
51+
{
52+
global $readStream;
53+
fclose($readStream);
54+
return false;
55+
}
56+
}
57+
58+
stream_register_wrapper('bomb', Bomb::class);
59+
$readStream = fopen('bomb://1', 'r');
60+
fread($readStream, 1);
61+
fwrite($readStream, "x", 1);
62+
fseek($readStream, 0, SEEK_SET);
63+
$streams = [$readStream];
64+
$empty = [];
65+
try {
66+
stream_select($streams, $streams,$empty, 0);
67+
} catch (ValueError $e) {
68+
echo $e->getMessage(), "\n";
69+
}
70+
fflush($readStream);
71+
try {
72+
fclose($readStream);
73+
} catch (TypeError $e) {
74+
echo $e->getMessage(), "\n";
75+
}
76+
77+
?>
78+
--EXPECTF--
79+
Warning: fclose(): cannot close the provided stream, as it must not be manually closed in %s on line %d
80+
81+
Warning: fclose(): cannot close the provided stream, as it must not be manually closed in %s on line %d
82+
83+
Warning: fclose(): cannot close the provided stream, as it must not be manually closed in %s on line %d
84+
85+
Warning: fclose(): cannot close the provided stream, as it must not be manually closed in %s on line %d
86+
87+
Warning: fclose(): cannot close the provided stream, as it must not be manually closed in %s on line %d
88+
89+
Warning: stream_select(): Cannot represent a stream of type user-space as a select()able descriptor in %s on line %d
90+
91+
Warning: fclose(): cannot close the provided stream, as it must not be manually closed in %s on line %d
92+
93+
Warning: stream_select(): Cannot represent a stream of type user-space as a select()able descriptor in %s on line %d
94+
No stream arrays were passed
95+
fclose(): Argument #1 ($stream) must be an open stream resource

‎main/streams/userspace.c

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,9 @@ static ssize_t php_userstreamop_write(php_stream *stream, const char *buf, size_
566566

567567
ZVAL_STRINGL(&args[0], (char*)buf, count);
568568

569+
uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE;
570+
stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
571+
569572
zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_WRITE, false);
570573
zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 1, args);
571574
zend_string_release_ex(func_name, false);
@@ -575,6 +578,10 @@ static ssize_t php_userstreamop_write(php_stream *stream, const char *buf, size_
575578
php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_WRITE " is not implemented!",
576579
ZSTR_VAL(us->wrapper->ce->name));
577580
}
581+
582+
stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
583+
stream->flags |= orig_no_fclose;
584+
578585
/* Exception occurred */
579586
if (Z_ISUNDEF(retval)) {
580587
return -1;
@@ -609,28 +616,31 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count
609616

610617
assert(us != NULL);
611618

619+
uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE;
620+
stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
621+
612622
ZVAL_LONG(&args[0], count);
613623
zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_READ, false);
614624
zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 1, args);
615625
zend_string_release_ex(func_name, false);
616626

617627
if (UNEXPECTED(Z_ISUNDEF(retval))) {
618-
return-1;
628+
goto err;
619629
}
620630

621631
if (UNEXPECTED(call_result == FAILURE)) {
622632
php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_READ " is not implemented!",
623633
ZSTR_VAL(us->wrapper->ce->name));
624-
return-1;
634+
goto err;
625635
}
626636

627637
if (Z_TYPE(retval) == IS_FALSE) {
628-
return-1;
638+
goto err;
629639
}
630640

631641
if (!try_convert_to_string(&retval)) {
632642
zval_ptr_dtor(&retval);
633-
return-1;
643+
goto err;
634644
}
635645

636646
didread = Z_STRLEN(retval);
@@ -657,19 +667,27 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count
657667
"%s::" USERSTREAM_EOF " is not implemented! Assuming EOF",
658668
ZSTR_VAL(us->wrapper->ce->name));
659669
stream->eof = 1;
660-
return-1;
670+
goto err;
661671
}
662672
if (UNEXPECTED(Z_ISUNDEF(retval))) {
663673
stream->eof = 1;
664-
return-1;
674+
goto err;
665675
}
666676

667677
if (zval_is_true(&retval)) {
668678
stream->eof = 1;
669679
}
670680
zval_ptr_dtor(&retval);
671681

682+
stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
683+
stream->flags |= orig_no_fclose;
684+
672685
return didread;
686+
687+
err:
688+
stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
689+
stream->flags |= orig_no_fclose;
690+
return -1;
673691
}
674692

675693
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
723741
ZVAL_LONG(&args[0], offset);
724742
ZVAL_LONG(&args[1], whence);
725743

744+
uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE;
745+
stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
746+
726747
zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_SEEK, false);
727748
zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 2, args);
728749
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
737758

738759
zval_ptr_dtor(&retval);
739760

740-
return -1;
761+
ret = -1;
762+
goto out;
741763
} else if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF && zval_is_true(&retval)) {
742764
ret = 0;
743765
} else {
@@ -748,7 +770,7 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when
748770
ZVAL_UNDEF(&retval);
749771

750772
if (ret) {
751-
returnret;
773+
goto out;
752774
}
753775

754776
/* now determine where we are */
@@ -767,6 +789,11 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when
767789
}
768790

769791
zval_ptr_dtor(&retval);
792+
793+
out:
794+
stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
795+
stream->flags |= orig_no_fclose;
796+
770797
return ret;
771798
}
772799

@@ -1394,6 +1421,9 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr)
13941421
break;
13951422
}
13961423

1424+
uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE;
1425+
stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
1426+
13971427
zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_CAST, false);
13981428
zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 1, args);
13991429
zend_string_release_ex(func_name, false);
@@ -1403,7 +1433,7 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr)
14031433
php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " is not implemented!",
14041434
ZSTR_VAL(us->wrapper->ce->name));
14051435
}
1406-
returnFAILURE;
1436+
goto out;
14071437
}
14081438

14091439
do {
@@ -1432,6 +1462,10 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr)
14321462

14331463
zval_ptr_dtor(&retval);
14341464

1465+
out:
1466+
stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
1467+
stream->flags |= orig_no_fclose;
1468+
14351469
return ret;
14361470
}
14371471

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /