Skip to content

Commit 4e35ee0

Browse files
iluuu1994saundefined
authored andcommitted
Fix use-after-free when unregistering user stream wrapper from itself
Fixes phpGH-11735 Closes phpGH-11737 (cherry picked from commit c3ccc36)
1 parent 8b1d352 commit 4e35ee0

File tree

3 files changed

+39
-4
lines changed

3 files changed

+39
-4
lines changed

Zend/tests/gh11735_1.phpt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
GH-11735: Use-after-free when unregistering user stream wrapper from user stream wrapper
3+
--FILE--
4+
<?php
5+
class FooWrapper {
6+
public $context;
7+
public function stream_open($path, $mode, $options, &$opened_path) {
8+
stream_wrapper_unregister('foo');
9+
return true;
10+
}
11+
}
12+
stream_wrapper_register('foo', 'FooWrapper');
13+
var_dump(fopen('foo://bar', 'r'));
14+
?>
15+
--EXPECTF--
16+
resource(%d) of type (stream)

Zend/tests/gh11735_2.phpt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
GH-11735: Use-after-free when unregistering user stream wrapper from user stream wrapper
3+
--FILE--
4+
<?php
5+
class FooWrapper {
6+
public $context;
7+
public function stream_open($path, $mode, $options, &$opened_path) {
8+
stream_wrapper_unregister('foo');
9+
return false;
10+
}
11+
}
12+
stream_wrapper_register('foo', 'FooWrapper');
13+
var_dump(fopen('foo://bar', 'r'));
14+
?>
15+
--EXPECTF--
16+
Warning: fopen(foo://bar): Failed to open stream: "FooWrapper::stream_open" call failed in %s on line %d
17+
bool(false)

main/streams/userspace.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,8 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *
341341

342342
us = emalloc(sizeof(*us));
343343
us->wrapper = uwrap;
344+
/* call_method_if_exists() may unregister the stream wrapper. Hold on to it. */
345+
GC_ADDREF(us->wrapper->resource);
344346

345347
user_stream_create_object(uwrap, context, &us->object);
346348
if (Z_TYPE(us->object) == IS_UNDEF) {
@@ -376,8 +378,6 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *
376378

377379
/* set wrapper data to be a reference to our object */
378380
ZVAL_COPY(&stream->wrapperdata, &us->object);
379-
380-
GC_ADDREF(us->wrapper->resource);
381381
} else {
382382
php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_OPEN "\" call failed",
383383
ZSTR_VAL(us->wrapper->ce->name));
@@ -387,6 +387,7 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *
387387
if (stream == NULL) {
388388
zval_ptr_dtor(&us->object);
389389
ZVAL_UNDEF(&us->object);
390+
zend_list_delete(us->wrapper->resource);
390391
efree(us);
391392
}
392393
zval_ptr_dtor(&zretval);
@@ -429,6 +430,8 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char
429430

430431
us = emalloc(sizeof(*us));
431432
us->wrapper = uwrap;
433+
/* call_method_if_exists() may unregister the stream wrapper. Hold on to it. */
434+
GC_ADDREF(us->wrapper->resource);
432435

433436
user_stream_create_object(uwrap, context, &us->object);
434437
if (Z_TYPE(us->object) == IS_UNDEF) {
@@ -451,8 +454,6 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char
451454

452455
/* set wrapper data to be a reference to our object */
453456
ZVAL_COPY(&stream->wrapperdata, &us->object);
454-
455-
GC_ADDREF(us->wrapper->resource);
456457
} else {
457458
php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_DIR_OPEN "\" call failed",
458459
ZSTR_VAL(us->wrapper->ce->name));
@@ -462,6 +463,7 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char
462463
if (stream == NULL) {
463464
zval_ptr_dtor(&us->object);
464465
ZVAL_UNDEF(&us->object);
466+
zend_list_delete(us->wrapper->resource);
465467
efree(us);
466468
}
467469
zval_ptr_dtor(&zretval);

0 commit comments

Comments
 (0)