Skip to content

Commit a37b056

Browse files
committed
streams: check class exists when attempting to register stream filter
And do a few other checks such as is the class even instantiable. It might make sense in the future to raise a deprecation if the class does not extend php_user_filter or create a new StreamFilter interface so that we know the various methods exist
1 parent 73e3e05 commit a37b056

4 files changed

Lines changed: 41 additions & 64 deletions

File tree

ext/standard/tests/filters/001.phpt

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,27 @@ stream_filter_register() and invalid arguments
33
--FILE--
44
<?php
55
try {
6-
stream_filter_register("", "");
7-
} catch (ValueError $exception) {
8-
echo $exception->getMessage() . "\n";
6+
stream_filter_register("", "stdClass");
7+
} catch (Throwable $e) {
8+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
99
}
1010

1111
try {
12-
stream_filter_register("test", "");
13-
} catch (ValueError $exception) {
14-
echo $exception->getMessage() . "\n";
12+
stream_filter_register("test", "Throwable");
13+
} catch (Throwable $e) {
14+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
1515
}
1616

1717
try {
18-
stream_filter_register("", "test");
19-
} catch (ValueError $exception) {
20-
echo $exception->getMessage() . "\n";
18+
var_dump(stream_filter_register("------", "nonexistentclass"));
19+
} catch (Throwable $e) {
20+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
2121
}
2222

23-
var_dump(stream_filter_register("------", "nonexistentclass"));
24-
2523
echo "Done\n";
2624
?>
2725
--EXPECT--
28-
stream_filter_register(): Argument #1 ($filter_name) must be a non-empty string
29-
stream_filter_register(): Argument #2 ($class) must be a non-empty string
30-
stream_filter_register(): Argument #1 ($filter_name) must be a non-empty string
31-
bool(true)
26+
ValueError: stream_filter_register(): Argument #1 ($filter_name) must be a non-empty string
27+
ValueError: stream_filter_register(): Argument #2 ($class) must be a concrete class
28+
TypeError: stream_filter_register(): Argument #2 ($class) must be a valid class name, nonexistentclass given
3229
Done

ext/standard/tests/filters/gh17037.phpt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
GH-17037 (UAF in user filter when adding existing filter name due to incorrect error handling)
33
--FILE--
44
<?php
5-
var_dump(stream_filter_register('string.toupper', 'filter_string_toupper'));
5+
try {
6+
var_dump(stream_filter_register('string.toupper', 'filter_string_toupper'));
7+
} catch (Throwable $e) {
8+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
9+
}
610
?>
711
--EXPECT--
8-
bool(false)
12+
TypeError: stream_filter_register(): Argument #2 ($class) must be a valid class name, filter_string_toupper given

ext/standard/tests/filters/stream_filter_register_non_existing_class.phpt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ stream_filter_register() with a class name that does not exist
33
--FILE--
44
<?php
55

6-
var_dump(stream_filter_register("not_existing_filter", "not_existing"));
6+
try {
7+
var_dump(stream_filter_register("not_existing_filter", "not_existing"));
8+
} catch (Throwable $e) {
9+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
10+
}
711

812
stream_filter_append(STDOUT, "not_existing_filter");
913

@@ -12,10 +16,8 @@ var_dump($out);
1216

1317
?>
1418
--EXPECTF--
15-
bool(true)
19+
TypeError: stream_filter_register(): Argument #2 ($class) must be a valid class name, not_existing given
1620

17-
Warning: stream_filter_append(): User-filter "not_existing_filter" requires class "not_existing", but that class is not defined in %s on line %d
18-
19-
Warning: stream_filter_append(): Unable to create or locate filter "not_existing_filter" in %s on line %d
21+
Warning: stream_filter_append(): Unable to locate filter "not_existing_filter" in %s on line %d
2022
Hello
2123
int(6)

ext/standard/user_filters.c

Lines changed: 16 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,6 @@
2323
#define PHP_STREAM_BRIGADE_RES_NAME "userfilter.bucket brigade"
2424
#define PHP_STREAM_BUCKET_RES_NAME "userfilter.bucket"
2525

26-
struct php_user_filter_data {
27-
zend_class_entry *ce;
28-
/* variable length; this *must* be last in the structure */
29-
zend_string *classname;
30-
};
31-
3226
/* to provide context for calling into the next filter from user-space */
3327
static int le_bucket_brigade;
3428
static int le_bucket;
@@ -328,7 +322,6 @@ static const php_stream_filter_ops userfilter_ops = {
328322
static php_stream_filter *user_filter_factory_create(const char *filtername,
329323
zval *filterparams, bool persistent)
330324
{
331-
struct php_user_filter_data *fdat = NULL;
332325
php_stream_filter *filter;
333326
zval obj;
334327
zval retval;
@@ -347,8 +340,9 @@ static php_stream_filter *user_filter_factory_create(const char *filtername,
347340

348341
len = strlen(filtername);
349342

350-
/* determine the classname/class entry */
351-
if (NULL == (fdat = zend_hash_str_find_ptr(BG(user_filter_map), filtername, len))) {
343+
/* determine the class entry */
344+
/* const */ zend_class_entry *ce = zend_hash_str_find_ptr(BG(user_filter_map), filtername, len);
345+
if (UNEXPECTED(ce == NULL)) {
352346
const char *period;
353347

354348
/* Userspace Filters using ambiguous wildcards could cause problems.
@@ -366,7 +360,8 @@ static php_stream_filter *user_filter_factory_create(const char *filtername,
366360
ZEND_ASSERT(new_period[0] == '.');
367361
new_period[1] = '*';
368362
new_period[2] = '\0';
369-
if (NULL != (fdat = zend_hash_str_find_ptr(BG(user_filter_map), wildcard, strlen(wildcard)))) {
363+
ce = zend_hash_str_find_ptr(BG(user_filter_map), wildcard, strlen(wildcard));
364+
if (NULL != ce) {
370365
new_period = NULL;
371366
} else {
372367
*new_period = '\0';
@@ -375,21 +370,11 @@ static php_stream_filter *user_filter_factory_create(const char *filtername,
375370
}
376371
efree(wildcard);
377372
}
378-
ZEND_ASSERT(fdat);
379-
}
380-
381-
/* bind the classname to the actual class */
382-
if (fdat->ce == NULL) {
383-
if (NULL == (fdat->ce = zend_lookup_class(fdat->classname))) {
384-
php_error_docref(NULL, E_WARNING,
385-
"User-filter \"%s\" requires class \"%s\", but that class is not defined",
386-
filtername, ZSTR_VAL(fdat->classname));
387-
return NULL;
388-
}
373+
ZEND_ASSERT(ce);
389374
}
390375

391376
/* create the object */
392-
if (object_init_ex(&obj, fdat->ce) == FAILURE) {
377+
if (object_init_ex(&obj, ce) == FAILURE) {
393378
return NULL;
394379
}
395380

@@ -435,13 +420,6 @@ static const php_stream_filter_factory user_filter_factory = {
435420
user_filter_factory_create
436421
};
437422

438-
static void filter_item_dtor(zval *zv)
439-
{
440-
struct php_user_filter_data *fdat = Z_PTR_P(zv);
441-
zend_string_release_ex(fdat->classname, 0);
442-
efree(fdat);
443-
}
444-
445423
/* {{{ Return a bucket object from the brigade for operating on */
446424
PHP_FUNCTION(stream_bucket_make_writeable)
447425
{
@@ -604,41 +582,37 @@ PHP_FUNCTION(stream_get_filters)
604582
/* {{{ Registers a custom filter handler class */
605583
PHP_FUNCTION(stream_filter_register)
606584
{
607-
zend_string *filtername, *classname;
608-
struct php_user_filter_data *fdat;
585+
zend_string *filtername;
586+
zend_class_entry *ce = NULL;
609587

610588
ZEND_PARSE_PARAMETERS_START(2, 2)
611589
Z_PARAM_STR(filtername)
612-
Z_PARAM_STR(classname)
590+
Z_PARAM_CLASS(ce)
613591
ZEND_PARSE_PARAMETERS_END();
614592

615593
if (!ZSTR_LEN(filtername)) {
616594
zend_argument_value_error(1, "must be a non-empty string");
617595
RETURN_THROWS();
618596
}
619597

620-
if (!ZSTR_LEN(classname)) {
621-
zend_argument_value_error(2, "must be a non-empty string");
598+
/* TODO: Check class is a child of php_user_filter? */
599+
if (UNEXPECTED(ce->ce_flags & ZEND_ACC_UNINSTANTIABLE)) {
600+
zend_argument_value_error(2, "must be a concrete class");
622601
RETURN_THROWS();
623602
}
624603

625604
if (!BG(user_filter_map)) {
626605
BG(user_filter_map) = (HashTable*) emalloc(sizeof(HashTable));
627-
zend_hash_init(BG(user_filter_map), 8, NULL, (dtor_func_t) filter_item_dtor, 0);
606+
/* We don't need a destructor as we are only storing a CE which should be never modified */
607+
zend_hash_init(BG(user_filter_map), 8, NULL, NULL, 0);
628608
}
629609

630-
fdat = ecalloc(1, sizeof(struct php_user_filter_data));
631-
fdat->classname = zend_string_copy(classname);
632-
633-
if (zend_hash_add_ptr(BG(user_filter_map), filtername, fdat) != NULL) {
610+
if (zend_hash_add_ptr(BG(user_filter_map), filtername, ce) != NULL) {
634611
if (php_stream_filter_register_factory_volatile(filtername, &user_filter_factory) == SUCCESS) {
635612
RETURN_TRUE;
636613
}
637614

638615
zend_hash_del(BG(user_filter_map), filtername);
639-
} else {
640-
zend_string_release_ex(classname, 0);
641-
efree(fdat);
642616
}
643617

644618
RETURN_FALSE;

0 commit comments

Comments
 (0)