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
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ PHP NEWS
- Streams:
. Fixed bug GH-15836 (Use-after-free when a user stream filter accesses
$this->stream during the close flush). (iliaal)
. Prevented stack exhaustion through unbounded recursion in user stream
wrappers whose nested opens use distinct filenames. (iliaal)

- Sysvshm:
. Fixed out-of-bounds write when shm_attach() opens an existing segment with
Expand Down
1 change: 1 addition & 0 deletions ext/standard/file.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ typedef struct {
char *user_agent; /* for the http wrapper */
char *from_address; /* for the ftp and http wrappers */
const char *user_stream_current_filename; /* for simple recursion protection */
int user_stream_recursion_depth;
php_stream_context *default_context;
HashTable *stream_wrappers; /* per-request copy of url_stream_wrappers_hash */
HashTable *stream_filters; /* per-request copy of stream_filters_hash */
Expand Down
25 changes: 25 additions & 0 deletions ext/standard/tests/streams/user_stream_recursion_unique_names.phpt
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
16 changes: 12 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 Down Expand Up @@ -379,6 +383,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 +407,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 +423,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 +461,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