Skip to content

Commit 70c3b33

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.
1 parent 1847d91 commit 70c3b33

File tree

1 file changed

+41
-8
lines changed

1 file changed

+41
-8
lines changed

main/streams/userspace.c

Lines changed: 41 additions & 8 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);
@@ -748,7 +769,7 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when
748769
ZVAL_UNDEF(&retval);
749770

750771
if (ret) {
751-
return ret;
772+
goto out;
752773
}
753774

754775
/* now determine where we are */
@@ -767,6 +788,11 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when
767788
}
768789

769790
zval_ptr_dtor(&retval);
791+
792+
out:
793+
stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
794+
stream->flags |= orig_no_fclose;
795+
770796
return ret;
771797
}
772798

@@ -1394,6 +1420,9 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr)
13941420
break;
13951421
}
13961422

1423+
uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE;
1424+
stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
1425+
13971426
zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_CAST, false);
13981427
zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 1, args);
13991428
zend_string_release_ex(func_name, false);
@@ -1403,7 +1432,7 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr)
14031432
php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " is not implemented!",
14041433
ZSTR_VAL(us->wrapper->ce->name));
14051434
}
1406-
return FAILURE;
1435+
goto out;
14071436
}
14081437

14091438
do {
@@ -1432,6 +1461,10 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr)
14321461

14331462
zval_ptr_dtor(&retval);
14341463

1464+
out:
1465+
stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
1466+
stream->flags |= orig_no_fclose;
1467+
14351468
return ret;
14361469
}
14371470

0 commit comments

Comments
 (0)