Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ PHP NEWS
PROCESS_INFORMATION, an indeterminate comspec pointer after a failed
lookup, and an unchecked CreateFileA() failure. (Ilia Alshanetsky)

- Streams:
. Fixed unbounded recursion when a userland stream wrapper opens a different
path at every nesting level. (Ilia Alshanetsky)

- XSL:
. Fixed bug GH-23730 (use-after-free when XSLTProcessor::importStylesheet()
is called during a transformation). (David Carlier)
Expand Down
1 change: 1 addition & 0 deletions ext/standard/file.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ typedef struct {
char *tmp_host_buf;
size_t tmp_host_buf_len;
#endif
int user_stream_recursion_depth;
} php_file_globals;

#ifdef ZTS
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--TEST--
User stream wrapper recursion is bounded even when each nested open uses a unique filename
--FILE--
<?php
class RecWrapper
{
public $context;
public static $depth = 0;
public function stream_open($path, $mode, $options, &$opened_path)
{
self::$depth++;
if (self::$depth >= 100000) {
return true;
}
@fopen('rec://' . self::$depth, 'r');
return true;
}
}
stream_wrapper_register('rec', 'RecWrapper');
@fopen('rec://start', 'r');
var_dump(RecWrapper::$depth < 100000);
echo "OK\n";
--EXPECT--
bool(true)
OK
1 change: 1 addition & 0 deletions main/streams/streams.c
Original file line number Diff line number Diff line change
Expand Up @@ -1886,6 +1886,7 @@ static void stream_resource_persistent_dtor(zend_resource *rsrc)
void php_shutdown_stream_hashes(void)
{
FG(user_stream_current_filename) = NULL;
FG(user_stream_recursion_depth) = 0;
if (FG(stream_wrappers)) {
zend_hash_destroy(FG(stream_wrappers));
efree(FG(stream_wrappers));
Expand Down
17 changes: 13 additions & 4 deletions main/streams/userspace.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@

static int le_protocols;

#define USER_STREAM_MAX_RECURSION_DEPTH 64

struct php_user_stream_wrapper {
php_stream_wrapper wrapper;
char * protoname;
Expand Down Expand Up @@ -302,12 +304,13 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *
php_stream *stream = NULL;
bool old_in_user_include;

/* Try to catch bad usage without preventing flexibility */
if (FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0) {
if ((FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0)
|| FG(user_stream_recursion_depth) >= USER_STREAM_MAX_RECURSION_DEPTH) {
php_stream_wrapper_log_error(wrapper, options, "infinite recursion prevented");
return NULL;
}
FG(user_stream_current_filename) = filename;
FG(user_stream_recursion_depth)++;

/* if the user stream was registered as local and we are in include context,
we add allow_url_include restrictions to allow_url_fopen ones */
Expand All @@ -328,6 +331,7 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *
user_stream_create_object(uwrap, context, &us->object);
if (Z_TYPE(us->object) == IS_UNDEF) {
FG(user_stream_current_filename) = NULL;
FG(user_stream_recursion_depth)--;
PG(in_user_include) = old_in_user_include;
efree(us);
return NULL;
Expand All @@ -345,6 +349,7 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *
call_result = call_method_if_exists(&us->object, &zfuncname, &zretval, 4, args);
} zend_catch {
FG(user_stream_current_filename) = NULL;
FG(user_stream_recursion_depth)--;
zend_bailout();
} zend_end_try();

Expand Down Expand Up @@ -379,6 +384,7 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *
zval_ptr_dtor(&args[0]);

FG(user_stream_current_filename) = NULL;
FG(user_stream_recursion_depth)--;

PG(in_user_include) = old_in_user_include;
return stream;
Expand All @@ -402,12 +408,13 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char
int call_result;
php_stream *stream = NULL;

/* Try to catch bad usage without preventing flexibility */
if (FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0) {
if ((FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0)
|| FG(user_stream_recursion_depth) >= USER_STREAM_MAX_RECURSION_DEPTH) {
php_stream_wrapper_log_error(wrapper, options, "infinite recursion prevented");
return NULL;
}
FG(user_stream_current_filename) = filename;
FG(user_stream_recursion_depth)++;

us = emalloc(sizeof(*us));
us->wrapper = uwrap;
Expand All @@ -417,6 +424,7 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char
user_stream_create_object(uwrap, context, &us->object);
if (Z_TYPE(us->object) == IS_UNDEF) {
FG(user_stream_current_filename) = NULL;
FG(user_stream_recursion_depth)--;
efree(us);
return NULL;
}
Expand Down Expand Up @@ -454,6 +462,7 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char
zval_ptr_dtor(&args[0]);

FG(user_stream_current_filename) = NULL;
FG(user_stream_recursion_depth)--;

return stream;
}
Expand Down
Loading