From 46352f7eadd0efd30d3eb3dcae17081d01ca0057 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Mon, 20 Jul 2026 21:23:43 +0100 Subject: [PATCH 1/6] 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. --- ext/standard/tests/gh22818.phpt | 28 ++++++++++++++++++++++++++++ ext/standard/user_filters.c | 17 +++++++++-------- 2 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 ext/standard/tests/gh22818.phpt diff --git a/ext/standard/tests/gh22818.phpt b/ext/standard/tests/gh22818.phpt new file mode 100644 index 000000000000..e6ebaeecce5d --- /dev/null +++ b/ext/standard/tests/gh22818.phpt @@ -0,0 +1,28 @@ +--TEST-- +Bug GH-22818: user_filter_factory_create assertion failure on shutdown re-registration +--FILE-- + +--EXPECTF-- +done + +Warning: stream_filter_append(): Unable to create or locate filter "rotator_notWorking" in %s on line %d diff --git a/ext/standard/user_filters.c b/ext/standard/user_filters.c index 9711ad1f83f2..d69af26912cc 100644 --- a/ext/standard/user_filters.c +++ b/ext/standard/user_filters.c @@ -601,20 +601,21 @@ PHP_FUNCTION(stream_filter_register) RETURN_THROWS(); } + /* Register the factory first; if that fails, don't (re)create the map, + * which would leak during shutdown re-registration. */ + if (php_stream_filter_register_factory_volatile(filtername, &user_filter_factory) == FAILURE) { + RETURN_FALSE; + } + if (!BG(user_filter_map)) { BG(user_filter_map) = (HashTable*) emalloc(sizeof(HashTable)); /* We don't need a destructor as we are only storing a CE which should be never modified */ zend_hash_init(BG(user_filter_map), 8, NULL, NULL, 0); } - if (zend_hash_add_ptr(BG(user_filter_map), filtername, ce) != NULL) { - if (php_stream_filter_register_factory_volatile(filtername, &user_filter_factory) == SUCCESS) { - RETURN_TRUE; - } - - zend_hash_del(BG(user_filter_map), filtername); - } + /* The factory has just been (re)registered, so keep the map in sync. */ + zend_hash_update_ptr(BG(user_filter_map), filtername, ce); - RETURN_FALSE; + RETURN_TRUE; } /* }}} */ From 33cdf36326319995a973d6009c0df34dcdc76292 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Thu, 30 Jul 2026 08:11:23 +0100 Subject: [PATCH 2/6] Use zend_hash_add_new_ptr() for the user_filter_map insertion. The volatile factory registration above already rejects a duplicate filter name, so the name cannot be in the map either. The add_new variant asserts that invariant in debug builds. Close GH-22838 --- NEWS | 2 ++ ext/standard/user_filters.c | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 425a48481a98..4e95bed34c84 100644 --- a/NEWS +++ b/NEWS @@ -29,6 +29,8 @@ PHP NEWS filegroup(), fileatime(), filemtime(), filectime(), filetype(), is_writable(), is_readable(), is_executable(), is_file(), is_dir(), is_link(), file_exists(), lstat(), stat(). (Girgias) + . Fixed bug GH-22818 (stream_filter_register() orphaned user_filter_map on + shutdown re-registration). (David Carlier) 30 Jul 2026, PHP 8.6.0alpha3 diff --git a/ext/standard/user_filters.c b/ext/standard/user_filters.c index d69af26912cc..e65ddd78ba4d 100644 --- a/ext/standard/user_filters.c +++ b/ext/standard/user_filters.c @@ -613,8 +613,9 @@ PHP_FUNCTION(stream_filter_register) zend_hash_init(BG(user_filter_map), 8, NULL, NULL, 0); } - /* The factory has just been (re)registered, so keep the map in sync. */ - zend_hash_update_ptr(BG(user_filter_map), filtername, ce); + /* The factory registration above already rejected a duplicate name, so the + * filter name cannot be present in the map either. */ + zend_hash_add_new_ptr(BG(user_filter_map), filtername, ce); RETURN_TRUE; } From f0d244ab3e97edf168d73a16da631823612b2789 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Fri, 31 Jul 2026 22:37:36 +0100 Subject: [PATCH 3/6] ext/sysvshm: do not trust $size when opening an existing segment shm_attach() wrote the requested size into the header of a segment it did not create, so a foreign segment framed as larger than it is let shm_put_var() write past the mapping. Take the size from shmctl() IPC_STAT instead, and reject a segment too small to hold the header. Noticed while reviewing GH-22959. --- NEWS | 4 +++ ext/sysvshm/sysvshm.c | 20 +++++++++++ .../shm_attach_existing_segment_size.phpt | 36 +++++++++++++++++++ ...shm_attach_existing_segment_too_small.phpt | 25 +++++++++++++ 4 files changed, 85 insertions(+) create mode 100644 ext/sysvshm/tests/shm_attach_existing_segment_size.phpt create mode 100644 ext/sysvshm/tests/shm_attach_existing_segment_too_small.phpt diff --git a/NEWS b/NEWS index d9c71758bf28..491d71681ff0 100644 --- a/NEWS +++ b/NEWS @@ -29,6 +29,10 @@ PHP NEWS . Fixed bug GH-15836 (Use-after-free when a user stream filter accesses $this->stream during the close flush). (iliaal) +- Sysvshm: + . Fixed out-of-bounds write when shm_attach() opens an existing segment with + a size larger than the segment actually is. (David Carlier) + - Opcache: . Fixed bug GH-22857 (Function JIT emits wrong code for FETCH_OBJ_FUNC_ARG on a property hook getter, losing register-held variables). (Zhao Hao) diff --git a/ext/sysvshm/sysvshm.c b/ext/sysvshm/sysvshm.c index 84c10add85ef..9c68c09147d4 100644 --- a/ext/sysvshm/sysvshm.c +++ b/ext/sysvshm/sysvshm.c @@ -130,6 +130,7 @@ PHP_FUNCTION(shm_attach) sysvshm_shm *shm_list_ptr; char *shm_ptr; sysvshm_chunk_head *chunk_ptr; + struct shmid_ds shm_desc; zend_long shm_key, shm_id, shm_size, shm_flag = 0666; bool shm_size_is_null = 1; bool created = false; @@ -168,6 +169,25 @@ PHP_FUNCTION(shm_attach) RETURN_FALSE; } + if (shmctl(shm_id, IPC_STAT, &shm_desc) < 0) { + php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": %s", shm_key, strerror(errno)); + shmdt(shm_ptr); + if (created) { + shmctl(shm_id, IPC_RMID, NULL); + } + RETURN_FALSE; + } + shm_size = (zend_long)shm_desc.shm_segsz; + + if (shm_size < (zend_long) sizeof(sysvshm_chunk_head)) { + php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": segment too small", shm_key); + shmdt(shm_ptr); + if (created) { + shmctl(shm_id, IPC_RMID, NULL); + } + RETURN_FALSE; + } + /* check if shm is already initialized */ chunk_ptr = (sysvshm_chunk_head *) shm_ptr; if (strcmp((char*) &(chunk_ptr->magic), "PHP_SM") != 0) { diff --git a/ext/sysvshm/tests/shm_attach_existing_segment_size.phpt b/ext/sysvshm/tests/shm_attach_existing_segment_size.phpt new file mode 100644 index 000000000000..8f7f233e0d5f --- /dev/null +++ b/ext/sysvshm/tests/shm_attach_existing_segment_size.phpt @@ -0,0 +1,36 @@ +--TEST-- +shm_attach() takes the size of an existing segment from the kernel, not from $size +--EXTENSIONS-- +sysvshm +shmop +--FILE-- + +--EXPECTF-- +bool(true) + +Warning: shm_put_var(): Not enough shared memory left in %s on line %d +bool(false) +bool(true) +string(2) "ok" +bool(true) +--CLEAN-- + diff --git a/ext/sysvshm/tests/shm_attach_existing_segment_too_small.phpt b/ext/sysvshm/tests/shm_attach_existing_segment_too_small.phpt new file mode 100644 index 000000000000..eec56c8f0919 --- /dev/null +++ b/ext/sysvshm/tests/shm_attach_existing_segment_too_small.phpt @@ -0,0 +1,25 @@ +--TEST-- +shm_attach() rejects an existing segment too small to hold its header +--EXTENSIONS-- +sysvshm +shmop +--FILE-- + +--EXPECTF-- +Warning: shm_attach(): Failed for key 0x%x: segment too small in %s on line %d +bool(false) +--CLEAN-- + From 395b29846e4bab536be017e98a523f9e94d5a204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81ukasik?= Date: Sun, 2 Aug 2026 23:30:37 +0200 Subject: [PATCH 4/6] configure.ac: register main/io as a build directory (#22913) PHP_ADD_SOURCES([main/io], ...) adds the new I/O subsystem sources (php_io.c, php_io_copy_*.c) to the build, but main/io was never added to the PHP_ADD_BUILD_DIR list. Since only directories in that list get pre-created for out-of-tree (VPATH) builds, main/io/ never existed in such build trees, causing: fatal error: opening dependency file main/io/php_io_copy_freebsd.dep: No such file or directory for every file in main/io/ when building outside the source tree (e.g. Debian's separate fpm-build/cli-build directories). Add main/io to PHP_ADD_BUILD_DIR, alongside the other source directories (main, main/poll, main/streams, TSRM, Zend, ...). --- configure.ac | 1 + 1 file changed, 1 insertion(+) diff --git a/configure.ac b/configure.ac index ec64637b186b..3229e0dac040 100644 --- a/configure.ac +++ b/configure.ac @@ -1834,6 +1834,7 @@ AC_DEFINE([HAVE_BUILD_DEFS_H], [1], PHP_ADD_BUILD_DIR([ main + main/io main/poll main/streams scripts From add8d542b6dd880ec2cbd834590276bcfd3e76fa Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 2 Aug 2026 20:19:06 +0100 Subject: [PATCH 5/6] Zend: use C enum for ZPP error states --- Zend/zend_API.c | 6 ++++-- Zend/zend_API.h | 34 ++++++++++++++++++---------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 71754b9ab755..84e7c8e1880e 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -225,7 +225,7 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_parameters_count_error(uint32_t } /* }}} */ -ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_parameter_error(int error_code, uint32_t num, char *name, zend_expected_type expected_type, const zval *arg) /* {{{ */ +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_parameter_error(zpp_error error_code, uint32_t num, char *name, zend_expected_type expected_type, const zval *arg) /* {{{ */ { switch (error_code) { case ZPP_ERROR_WRONG_CALLBACK: @@ -261,7 +261,9 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_parameter_error(int error_code, case ZPP_ERROR_FAILURE: ZEND_ASSERT(EG(exception) && "Should have produced an error already"); break; - default: ZEND_UNREACHABLE(); + case ZPP_ERROR_OK: + case ZPP_ERROR_WRONG_COUNT: + ZEND_UNREACHABLE(); } } /* }}} */ diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 34c316bca588..e83aec34ef0e 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -1588,9 +1588,25 @@ typedef enum _zend_expected_type { Z_EXPECTED_LAST } zend_expected_type; +C23_ENUM(zpp_error, uint8_t) { + ZPP_ERROR_OK, + ZPP_ERROR_FAILURE, + ZPP_ERROR_WRONG_CALLBACK, + ZPP_ERROR_WRONG_CLASS, + ZPP_ERROR_WRONG_CLASS_OR_NULL, + ZPP_ERROR_WRONG_CLASS_OR_STRING, + ZPP_ERROR_WRONG_CLASS_OR_STRING_OR_NULL, + ZPP_ERROR_WRONG_CLASS_OR_LONG, + ZPP_ERROR_WRONG_CLASS_OR_LONG_OR_NULL, + ZPP_ERROR_WRONG_ARG, + ZPP_ERROR_WRONG_COUNT, + ZPP_ERROR_UNEXPECTED_EXTRA_NAMED, + ZPP_ERROR_WRONG_CALLBACK_OR_NULL, +}; + ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_parameters_none_error(void); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_parameters_count_error(uint32_t min_num_args, uint32_t max_num_args); -ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_parameter_error(int error_code, uint32_t num, char *name, zend_expected_type expected_type, const zval *arg); +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_parameter_error(zpp_error error_code, uint32_t num, char *name, zend_expected_type expected_type, const zval *arg); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_parameter_type_error(uint32_t num, zend_expected_type expected_type, const zval *arg); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_parameter_class_error(uint32_t num, const char *name, const zval *arg); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_parameter_class_or_null_error(uint32_t num, const char *name, const zval *arg); @@ -1612,20 +1628,6 @@ ZEND_API ZEND_COLD void zend_argument_must_not_be_empty_error(uint32_t arg_num); ZEND_API ZEND_COLD void zend_class_redeclaration_error(int type, const zend_class_entry *old_ce); ZEND_API ZEND_COLD void zend_class_redeclaration_error_ex(int type, zend_string *new_name, const zend_class_entry *old_ce); -#define ZPP_ERROR_OK 0 -#define ZPP_ERROR_FAILURE 1 -#define ZPP_ERROR_WRONG_CALLBACK 2 -#define ZPP_ERROR_WRONG_CLASS 3 -#define ZPP_ERROR_WRONG_CLASS_OR_NULL 4 -#define ZPP_ERROR_WRONG_CLASS_OR_STRING 5 -#define ZPP_ERROR_WRONG_CLASS_OR_STRING_OR_NULL 6 -#define ZPP_ERROR_WRONG_CLASS_OR_LONG 7 -#define ZPP_ERROR_WRONG_CLASS_OR_LONG_OR_NULL 8 -#define ZPP_ERROR_WRONG_ARG 9 -#define ZPP_ERROR_WRONG_COUNT 10 -#define ZPP_ERROR_UNEXPECTED_EXTRA_NAMED 11 -#define ZPP_ERROR_WRONG_CALLBACK_OR_NULL 12 - #define ZEND_PARSE_PARAMETERS_START_EX(flags, min_num_args, max_num_args) do { \ const int _flags = (flags); \ uint32_t _min_num_args = (min_num_args); \ @@ -1637,7 +1639,7 @@ ZEND_API ZEND_COLD void zend_class_redeclaration_error_ex(int type, zend_string char *_error = NULL; \ bool _dummy = 0; \ bool _optional = 0; \ - int _error_code = ZPP_ERROR_OK; \ + zpp_error _error_code = ZPP_ERROR_OK; \ ((void)_i); \ ((void)_real_arg); \ ((void)_arg); \ From bbf82d7a84c4fa10fd57ab438082e322758ba373 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 2 Aug 2026 20:25:27 +0100 Subject: [PATCH 6/6] Zend: Remove ZPP_ERROR_WRONG_COUNT case As it is unused, the relevant error is handled before and _error_code is assigned ZPP_ERROR_FAILURE. --- Zend/zend_API.c | 1 - Zend/zend_API.h | 1 - 2 files changed, 2 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 84e7c8e1880e..758977e07d6a 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -262,7 +262,6 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_parameter_error(zpp_error error ZEND_ASSERT(EG(exception) && "Should have produced an error already"); break; case ZPP_ERROR_OK: - case ZPP_ERROR_WRONG_COUNT: ZEND_UNREACHABLE(); } } diff --git a/Zend/zend_API.h b/Zend/zend_API.h index e83aec34ef0e..a3e4e1690d6c 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -1599,7 +1599,6 @@ C23_ENUM(zpp_error, uint8_t) { ZPP_ERROR_WRONG_CLASS_OR_LONG, ZPP_ERROR_WRONG_CLASS_OR_LONG_OR_NULL, ZPP_ERROR_WRONG_ARG, - ZPP_ERROR_WRONG_COUNT, ZPP_ERROR_UNEXPECTED_EXTRA_NAMED, ZPP_ERROR_WRONG_CALLBACK_OR_NULL, };