Skip to content

Commit 42d5158

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 cb04226 commit 42d5158

File tree

1 file changed

+37
-6
lines changed

1 file changed

+37
-6
lines changed

main/streams/userspace.c

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -575,10 +575,16 @@ static ssize_t php_userstreamop_write(php_stream *stream, const char *buf, size_
575575

576576
ZVAL_STRINGL(&args[0], (char*)buf, count);
577577

578+
uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE;
579+
stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
580+
578581
call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args);
579582
zval_ptr_dtor(&args[0]);
580583
zval_ptr_dtor(&func_name);
581584

585+
stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
586+
stream->flags |= orig_no_fclose;
587+
582588
if (EG(exception)) {
583589
return -1;
584590
}
@@ -620,6 +626,9 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count
620626

621627
assert(us != NULL);
622628

629+
uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE;
630+
stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
631+
623632
ZVAL_STRINGL(&func_name, USERSTREAM_READ, sizeof(USERSTREAM_READ)-1);
624633

625634
ZVAL_LONG(&args[0], count);
@@ -630,22 +639,22 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count
630639
zval_ptr_dtor(&func_name);
631640

632641
if (EG(exception)) {
633-
return -1;
642+
goto err;
634643
}
635644

636645
if (call_result == FAILURE) {
637646
php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_READ " is not implemented!",
638647
ZSTR_VAL(us->wrapper->ce->name));
639-
return -1;
648+
goto err;
640649
}
641650

642651
if (Z_TYPE(retval) == IS_FALSE) {
643-
return -1;
652+
goto err;
644653
}
645654

646655
if (!try_convert_to_string(&retval)) {
647656
zval_ptr_dtor(&retval);
648-
return -1;
657+
goto err;
649658
}
650659

651660
didread = Z_STRLEN(retval);
@@ -669,7 +678,7 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count
669678

670679
if (EG(exception)) {
671680
stream->eof = 1;
672-
return -1;
681+
goto err;
673682
}
674683

675684
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
684693

685694
zval_ptr_dtor(&retval);
686695

696+
stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
697+
stream->flags |= orig_no_fclose;
698+
687699
return didread;
700+
701+
err:
702+
stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
703+
stream->flags |= orig_no_fclose;
704+
return -1;
688705
}
689706

690707
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
749766
ZVAL_LONG(&args[0], offset);
750767
ZVAL_LONG(&args[1], whence);
751768

769+
uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE;
770+
stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
771+
752772
call_result = call_method_if_exists(&us->object, &func_name, &retval, 2, args);
753773

754774
zval_ptr_dtor(&args[0]);
@@ -773,7 +793,7 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when
773793
ZVAL_UNDEF(&retval);
774794

775795
if (ret) {
776-
return ret;
796+
goto out;
777797
}
778798

779799
/* now determine where we are */
@@ -793,6 +813,11 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when
793813

794814
zval_ptr_dtor(&retval);
795815
zval_ptr_dtor(&func_name);
816+
817+
out:
818+
stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
819+
stream->flags |= orig_no_fclose;
820+
796821
return ret;
797822
}
798823

@@ -1403,6 +1428,9 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr)
14031428
break;
14041429
}
14051430

1431+
uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE;
1432+
stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
1433+
14061434
call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args);
14071435

14081436
do {
@@ -1439,6 +1467,9 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr)
14391467
zval_ptr_dtor(&func_name);
14401468
zval_ptr_dtor(&args[0]);
14411469

1470+
stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
1471+
stream->flags |= orig_no_fclose;
1472+
14421473
return ret;
14431474
}
14441475

0 commit comments

Comments
 (0)