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/Zend/zend_API.c b/Zend/zend_API.c index 71754b9ab755..758977e07d6a 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,8 @@ 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: + ZEND_UNREACHABLE(); } } /* }}} */ diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 34c316bca588..a3e4e1690d6c 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -1588,9 +1588,24 @@ 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_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 +1627,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 +1638,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); \ 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 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..e65ddd78ba4d 100644 --- a/ext/standard/user_filters.c +++ b/ext/standard/user_filters.c @@ -601,20 +601,22 @@ 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 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_FALSE; + RETURN_TRUE; } /* }}} */ diff --git a/ext/sysvshm/sysvshm.c b/ext/sysvshm/sysvshm.c index 244032906772..448bec898645 100644 --- a/ext/sysvshm/sysvshm.c +++ b/ext/sysvshm/sysvshm.c @@ -126,6 +126,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_arg, shm_id, shm_size, shm_flag = 0666; key_t shm_key; bool shm_size_is_null = true; @@ -171,6 +172,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_arg, 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_arg); + 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-- +