Skip to content

Commit 46352f7

Browse files
committed
ext/standard: stream_filter_register() orphaned user_filter_map on shutdown re-registration.
Fix GH-22818 During request shutdown the user_filter_map is torn down by the user_filters RSHUTDOWN before the streams referencing it are flushed. A user filter whose filter() callback re-registers the filter recreated the now-NULL map, then, since the volatile factory was still present in FG(stream_filters), deleted the freshly added entry and left an empty orphaned map behind. The following stream_filter_append() located the factory but no matching fdat, tripping ZEND_ASSERT(fdat), and the recreated map leaked. Register the volatile factory first and only create and populate user_filter_map on success, so a re-registration during the shutdown window fails without recreating the map. The existing NULL-map guard in user_filter_factory_create() then handles the append gracefully.
1 parent e31d8e0 commit 46352f7

2 files changed

Lines changed: 37 additions & 8 deletions

File tree

ext/standard/tests/gh22818.phpt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
Bug GH-22818: user_filter_factory_create assertion failure on shutdown re-registration
3+
--FILE--
4+
<?php
5+
6+
class rotate_filter_nw extends php_user_filter
7+
{
8+
public function filter($in, $out, &$consumed, $closing): int
9+
{
10+
$stream = fopen('php://memory', 'w+');
11+
stream_filter_register("rotator_notWorking", rotate_filter_nw::class);
12+
stream_filter_append($stream, "rotator_notWorking");
13+
14+
return PSFS_PASS_ON;
15+
}
16+
}
17+
18+
stream_filter_register("rotator_notWorking", rotate_filter_nw::class);
19+
20+
$stream = fopen('php://memory', 'w+');
21+
stream_filter_append($stream, "rotator_notWorking");
22+
23+
echo "done\n";
24+
?>
25+
--EXPECTF--
26+
done
27+
28+
Warning: stream_filter_append(): Unable to create or locate filter "rotator_notWorking" in %s on line %d

ext/standard/user_filters.c

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -601,20 +601,21 @@ PHP_FUNCTION(stream_filter_register)
601601
RETURN_THROWS();
602602
}
603603

604+
/* Register the factory first; if that fails, don't (re)create the map,
605+
* which would leak during shutdown re-registration. */
606+
if (php_stream_filter_register_factory_volatile(filtername, &user_filter_factory) == FAILURE) {
607+
RETURN_FALSE;
608+
}
609+
604610
if (!BG(user_filter_map)) {
605611
BG(user_filter_map) = (HashTable*) emalloc(sizeof(HashTable));
606612
/* We don't need a destructor as we are only storing a CE which should be never modified */
607613
zend_hash_init(BG(user_filter_map), 8, NULL, NULL, 0);
608614
}
609615

610-
if (zend_hash_add_ptr(BG(user_filter_map), filtername, ce) != NULL) {
611-
if (php_stream_filter_register_factory_volatile(filtername, &user_filter_factory) == SUCCESS) {
612-
RETURN_TRUE;
613-
}
614-
615-
zend_hash_del(BG(user_filter_map), filtername);
616-
}
616+
/* The factory has just been (re)registered, so keep the map in sync. */
617+
zend_hash_update_ptr(BG(user_filter_map), filtername, ce);
617618

618-
RETURN_FALSE;
619+
RETURN_TRUE;
619620
}
620621
/* }}} */

0 commit comments

Comments
 (0)