From 1d9031b957d7aeff3189ac50ad87023cd1eeb2e3 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 12 Oct 2026 14:31:47 +0100 Subject: [PATCH 1/3] Zend: an empty class name is not valid --- Zend/zend_execute_API.c | 6 ++++-- ...erialize_allowed_classes_option_invalid_class_names.phpt | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index beb3f9f995af..c9507bc33a47 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1220,10 +1220,12 @@ ZEND_API bool zend_is_valid_class_name(const zend_string *name) { for (size_t i = 0; i < ZSTR_LEN(name); i++) { unsigned char c = ZSTR_VAL(name)[i]; if (!ZEND_BIT_TEST(valid_chars, c)) { - return 0; + return false; } } - return 1; + + /* An empty string is *not* a valid class name */ + return ZSTR_LEN(name); } ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string *key, uint32_t flags) /* {{{ */ diff --git a/ext/standard/tests/serialize/unserialize_allowed_classes_option_invalid_class_names.phpt b/ext/standard/tests/serialize/unserialize_allowed_classes_option_invalid_class_names.phpt index 74997939817d..827ce20085ce 100644 --- a/ext/standard/tests/serialize/unserialize_allowed_classes_option_invalid_class_names.phpt +++ b/ext/standard/tests/serialize/unserialize_allowed_classes_option_invalid_class_names.phpt @@ -41,6 +41,7 @@ try { ?> --EXPECT-- +ValueError: unserialize(): Option "allowed_classes" must be an array of class names, "" given ValueError: unserialize(): Option "allowed_classes" must be an array of class names, " whitespace " given ValueError: unserialize(): Option "allowed_classes" must be an array of class names, "name with whitespace" given From b1dac11e7bd1968d6658b893a2a86ec10633e04b Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 14 Oct 2026 14:08:56 +0100 Subject: [PATCH 2/3] Zend: simplify logic in zend_lookup_class_ex() Possible now that zend_is_valid_class_name() correctly rejects empty strings. --- Zend/zend_execute_API.c | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index c9507bc33a47..2bc38880fe8c 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1244,17 +1244,11 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * } if (key) { + ZEND_ASSERT(ZSTR_LEN(name) != 0 && "Must not be an empty class name"); + ZEND_ASSERT((ZSTR_VAL(name)[0] != '\\' || ZSTR_LEN(name) > 1) && "Class name must not be \"\\\""); lc_name = key; } else { - if (!ZSTR_LEN(name)) { - return NULL; - } - if (ZSTR_VAL(name)[0] == '\\') { - if (ZSTR_LEN(name) == 1) { - /* A lone namespace separator names no class, e.g. is_callable('\::method'). */ - return NULL; - } lc_name = zend_string_alloc(ZSTR_LEN(name) - 1, 0); zend_str_tolower_copy(ZSTR_VAL(lc_name), ZSTR_VAL(name) + 1, ZSTR_LEN(name) - 1); } else { @@ -1289,26 +1283,25 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * return ce; } - /* The compiler is not-reentrant. Make sure we autoload only during run-time. */ - if ((flags & ZEND_FETCH_CLASS_NO_AUTOLOAD) || zend_is_compiling()) { - if (!key) { - zend_string_release_ex(lc_name, 0); - } - return NULL; - } - - if (!zend_autoload) { + if ( + (flags & ZEND_FETCH_CLASS_NO_AUTOLOAD) + || !zend_autoload + /* The compiler is not-reentrant. Make sure we autoload only during run-time. */ + || zend_is_compiling() + ) { if (!key) { zend_string_release_ex(lc_name, 0); } return NULL; } - /* Verify class name before passing it to the autoloader. */ - if (!key && !ZSTR_HAS_CE_CACHE(name) && !zend_is_valid_class_name(name)) { + /* Verify class name before passing it to the autoloader. + * We check for the lowercase name as it already in canonical form without leading \ */ + if (!key && !ZSTR_HAS_CE_CACHE(name) && !zend_is_valid_class_name(lc_name)) { zend_string_release_ex(lc_name, 0); return NULL; } + ZEND_ASSERT(zend_is_valid_class_name(name) && "If lowercase name is valid the name must also be so"); if (zend_hash_add_empty_element(&EG(autoload_current_classnames), lc_name) == NULL) { if (!key) { From d1e51dbfea8b4fed576dc7b507846243e704c917 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 14 Oct 2026 14:10:49 +0100 Subject: [PATCH 3/3] Zend: use %pS printf specifier in zend_is_callable_check_class() --- Zend/zend_API.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index c3a0b4cfbafb..ec487d4ee0bf 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3821,7 +3821,7 @@ static bool zend_is_callable_check_class(zend_string *name, zend_class_entry *sc *strict_class = true; ret = true; } else { - if (error) zend_spprintf(error, 0, "class \"%.*s\" not found", (int)name_len, ZSTR_VAL(name)); + if (error) zend_spprintf(error, 0, "class \"%pS\" not found", name); } ZSTR_ALLOCA_FREE(lcname, use_heap); /* User error handlers may throw from deprecations above; do not report callable as valid. */