From aae5edb8e360e5d08d331cce4556ba4cf1ed3238 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 2 Jul 2026 23:40:25 +0200 Subject: [PATCH 1/4] Implement perf stat --control for cgi (GH-22537) perf stat --control allows the profiled process to enable and disable counters at runtime. This allows us to skip profiling startup and shutdown for more accurate results. The same already exists for valgrind. $ perf stat -D -1 --control fifo:/tmp/perfctl,/tmp/perfack -D -1 starts perf stat with counters disabled. --control makes perf stat connect to the /tmp/perfctl and /tmp/perfack fifo files. These need to exist when starting perf, so create them using mkfifo /tmp/perf{ctl,ack}. The ctl fifo is written to by cgi to enable/disable counters, whereas the ack fifo is written to by perf stat to acknowledge counters have been installed. Additionally, you'll need to set the set the PERF_STAT_CTL_FIFO and PERF_STAT_ACK_FIFO env variables for cgi to find the fifo files. --- Zend/zend_perf_stat.h | 135 ++++++++++++++++++++++++++++++++++++++++++ sapi/cgi/cgi_main.c | 13 +++- 2 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 Zend/zend_perf_stat.h diff --git a/Zend/zend_perf_stat.h b/Zend/zend_perf_stat.h new file mode 100644 index 000000000000..447315de0133 --- /dev/null +++ b/Zend/zend_perf_stat.h @@ -0,0 +1,135 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright © Zend Technologies Ltd., a subsidiary company of | + | Perforce Software, Inc., and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Authors: Ilija Tovilo | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_PERF_STAT_H +#define ZEND_PERF_STAT_H + +#include "zend_portability.h" + +# if !(HAVE_FCNTL_H && HAVE_SYS_SELECT_H && HAVE_SYS_STAT_H && HAVE_SYS_TIME_H && HAVE_UNISTD_H) +static void zend_perf_stat_enable(void) {} +static void zend_perf_stat_disable(void) {} +# else + +# include +# include +# include +# include + +# include "Zend/zend.h" + +# define ZPS_CTL_FIFO_ENV "PERF_STAT_CTL_FIFO" +# define ZPS_ACK_FIFO_ENV "PERF_STAT_ACK_FIFO" + +static int ctl_fd = -2; +static int ack_fd = -2; + +static int zps_open_fifo(const char *env_name, int flags) +{ + const char *path = getenv(env_name); + + if (path == NULL || path[0] == '\0') { + return -1; + } + +# ifdef O_CLOEXEC + flags |= O_CLOEXEC; +# endif + int fd = open(path, flags | O_NONBLOCK); + if (fd < 0) { + fprintf(stderr, "Failed to open fifo %s\n", path); + fflush(stderr); + zend_bailout(); + } + + struct stat st; + if (fstat(fd, &st) != 0 || !S_ISFIFO(st.st_mode)) { + close(fd); + fprintf(stderr, "File %s is not a fifo\n", path); + fflush(stderr); + zend_bailout(); + } + return fd; +} + +static void zps_init(void) +{ + if (ctl_fd == -2) { + ctl_fd = zps_open_fifo(ZPS_CTL_FIFO_ENV, O_WRONLY); + } + if (ack_fd == -2) { + ack_fd = zps_open_fifo(ZPS_ACK_FIFO_ENV, O_RDONLY); + } +} + +static void zps_wait_ack(void) +{ + if (ack_fd < 0) { + return; + } + + struct timeval timeout; + timeout.tv_sec = 1; + timeout.tv_usec = 0; + + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(ack_fd, &readfds); + if (select(ack_fd + 1, &readfds, NULL, NULL, &timeout) <= 0) { + return; + } + + char ack[sizeof("ack\n") - 1]; + ssize_t bytes_read; + do { + bytes_read = read(ack_fd, ack, sizeof(ack)); + } while (bytes_read > 0); +} + +static void zps_control(const char *command, size_t command_len) +{ + zps_init(); + + if (ctl_fd < 0) { + return; + } + + if (write(ctl_fd, command, command_len) != (ssize_t) command_len) { + close(ctl_fd); + ctl_fd = -1; + return; + } + + zps_wait_ack(); +} + +static void zend_perf_stat_enable(void) +{ + static const char command[] = "enable\n"; + + zps_control(command, sizeof(command) - 1); +} + +static void zend_perf_stat_disable(void) +{ + static const char command[] = "disable\n"; + + zps_control(command, sizeof(command) - 1); +} + +# endif +#endif diff --git a/sapi/cgi/cgi_main.c b/sapi/cgi/cgi_main.c index f49507827e2d..7efd8f159d59 100644 --- a/sapi/cgi/cgi_main.c +++ b/sapi/cgi/cgi_main.c @@ -95,6 +95,8 @@ int __riscosify_control = __RISCOSIFY_STRICT_UNIX_SPECS; # endif #endif +#include "zend_perf_stat.h" + #ifndef PHP_WIN32 /* XXX this will need to change later when threaded fastcgi is implemented. shane */ static struct sigaction act, old_term, old_quit, old_int; @@ -1733,6 +1735,7 @@ int main(int argc, char *argv[]) int warmup_repeats = 0; int repeats = 1; int benchmark = 0; + bool perf_enabled = false; #ifdef HAVE_GETTIMEOFDAY struct timeval start, end; #else @@ -2441,14 +2444,16 @@ consult the installation file that came with this distribution, or visit \n\ } } /* end !cgi && !fastcgi */ -#ifdef HAVE_VALGRIND if (warmup_repeats == 0) { + zend_perf_stat_enable(); + perf_enabled = true; +#ifdef HAVE_VALGRIND CALLGRIND_START_INSTRUMENTATION; # ifdef HAVE_VALGRIND_CACHEGRIND_H CACHEGRIND_START_INSTRUMENTATION; # endif - } #endif + } /* request startup only after we've done all we can to * get path_translated */ @@ -2568,6 +2573,10 @@ consult the installation file that came with this distribution, or visit \n\ SG(request_info).query_string = NULL; } + if (perf_enabled) { + zend_perf_stat_disable(); + perf_enabled = false; + } #ifdef HAVE_VALGRIND /* We're not interested in measuring shutdown */ CALLGRIND_STOP_INSTRUMENTATION; From 76dad0667f7a756aa65bcc1d26cbe6cf87fcdca0 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 15:30:46 -0700 Subject: [PATCH 2/4] [8.5] NEWS: add entry for bugfix from GH-22187 --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index fceef3f9a3c1..40cbc17db654 100644 --- a/NEWS +++ b/NEWS @@ -122,6 +122,10 @@ PHP NEWS . Fixed bug GH-21972 (Corrupted variable type when a typed by-value return contains a reference wrapper). (Weilin Du) +- OpenSSL: + . Fixed bug GH-22187 (Memory corruption (zend_mm_heap corrupted) in + openssl_encrypt with AES-WRAP-PAD). (David Carlier) + - Phar: . Fixed a bypass of the magic ".phar" directory protection in Phar::addEmptyDir() for paths starting with "/.phar", while allowing From 39b66bae333eabb173476d09de1106890bc94980 Mon Sep 17 00:00:00 2001 From: Jorg Adam Sowa Date: Fri, 3 Jul 2026 01:53:33 +0200 Subject: [PATCH 3/4] Use zend_hash_find_ptr_lc() for case-insensitive name lookups (#22565) A number of call sites lowercased a class/function/method name into a temporary string solely to use it as a lookup key in the class table, function table, module registry, etc., then released it. Replace those with zend_hash_find_ptr_lc() / zend_hash_str_find_ptr_lc(), which lowercase into a stack buffer (for short names), perform the lookup, and free the temporary internally. This removes the manual tolower + release boilerplate at 17 sites and avoids a heap allocation for the common short-name case. No behavior change. --- Zend/Optimizer/zend_optimizer.c | 8 ++------ Zend/zend_builtin_functions.c | 10 ++-------- Zend/zend_inheritance.c | 19 ++++--------------- ext/hash/hash.c | 4 +--- ext/opcache/ZendAccelerator.c | 4 +--- ext/pdo/pdo_dbh.c | 5 +---- ext/reflection/php_reflection.c | 5 +---- sapi/cli/php_cli.c | 4 +--- sapi/phpdbg/phpdbg_bp.c | 18 +++--------------- sapi/phpdbg/phpdbg_list.c | 5 +---- 10 files changed, 17 insertions(+), 65 deletions(-) diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index d10b4d83fc3e..0173d2ef47a2 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -104,9 +104,7 @@ zend_result zend_optimizer_eval_special_func_call( zval *result, const zend_string *name, zend_string *arg) { if (zend_string_equals_literal(name, "function_exists") || zend_string_equals_literal(name, "is_callable")) { - zend_string *lc_name = zend_string_tolower(arg); - const zend_internal_function *func = zend_hash_find_ptr(EG(function_table), lc_name); - zend_string_release_ex(lc_name, 0); + const zend_internal_function *func = zend_hash_find_ptr_lc(EG(function_table), arg); if (func && func->type == ZEND_INTERNAL_FUNCTION && func->module->type == MODULE_PERSISTENT @@ -120,9 +118,7 @@ zend_result zend_optimizer_eval_special_func_call( return FAILURE; } if (zend_string_equals_literal(name, "extension_loaded")) { - zend_string *lc_name = zend_string_tolower(arg); - zend_module_entry *m = zend_hash_find_ptr(&module_registry, lc_name); - zend_string_release_ex(lc_name, 0); + zend_module_entry *m = zend_hash_find_ptr_lc(&module_registry, arg); if (!m) { if (PG(enable_dl)) { diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 2dceac2512db..87acf073a2da 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -953,7 +953,6 @@ ZEND_FUNCTION(method_exists) { zval *klass; zend_string *method_name; - zend_string *lcname; zend_class_entry *ce; zend_function *func; @@ -974,9 +973,7 @@ ZEND_FUNCTION(method_exists) RETURN_THROWS(); } - lcname = zend_string_tolower(method_name); - func = zend_hash_find_ptr(&ce->function_table, lcname); - zend_string_release_ex(lcname, 0); + func = zend_hash_find_ptr_lc(&ce->function_table, method_name); if (func) { /* Exclude shadow properties when checking a method on a specific class. Include @@ -2219,7 +2216,6 @@ ZEND_FUNCTION(extension_loaded) ZEND_FUNCTION(get_extension_funcs) { zend_string *extension_name; - zend_string *lcname; bool array; zend_module_entry *module; zend_function *zif; @@ -2228,9 +2224,7 @@ ZEND_FUNCTION(get_extension_funcs) RETURN_THROWS(); } if (strncasecmp(ZSTR_VAL(extension_name), "zend", sizeof("zend"))) { - lcname = zend_string_tolower(extension_name); - module = zend_hash_find_ptr(&module_registry, lcname); - zend_string_release_ex(lcname, 0); + module = zend_hash_find_ptr_lc(&module_registry, extension_name); } else { module = zend_hash_str_find_ptr(&module_registry, "core", sizeof("core") - 1); } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 8df6a5599d30..848b9d209b2d 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -264,11 +264,7 @@ static zend_class_entry *lookup_class_ex( bool in_preload = CG(compiler_options) & ZEND_COMPILE_PRELOAD; if (UNEXPECTED(!EG(active) && !in_preload)) { - zend_string *lc_name = zend_string_tolower(name); - - ce = zend_hash_find_ptr(CG(class_table), lc_name); - - zend_string_release(lc_name); + ce = zend_hash_find_ptr_lc(CG(class_table), name); if (register_unresolved && !ce) { zend_error_noreturn( @@ -2530,7 +2526,6 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_e size_t i, j = 0; zend_trait_precedence *cur_precedence; zend_trait_method_reference *cur_method_ref; - zend_string *lc_trait_name; zend_string *lcname; HashTable **exclude_tables = NULL; zend_class_entry **aliases = NULL; @@ -2545,9 +2540,7 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_e while ((cur_precedence = precedences[i])) { /** Resolve classes for all precedence operations. */ cur_method_ref = &cur_precedence->trait_method; - lc_trait_name = zend_string_tolower(cur_method_ref->class_name); - trait = zend_hash_find_ptr(EG(class_table), lc_trait_name); - zend_string_release_ex(lc_trait_name, 0); + trait = zend_hash_find_ptr_lc(EG(class_table), cur_method_ref->class_name); if (!trait || !(trait->ce_flags & ZEND_ACC_LINKED)) { zend_error_noreturn(E_COMPILE_ERROR, "Could not find trait %s", ZSTR_VAL(cur_method_ref->class_name)); } @@ -2574,9 +2567,7 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_e zend_class_entry *exclude_ce; uint32_t trait_num; - lc_trait_name = zend_string_tolower(class_name); - exclude_ce = zend_hash_find_ptr(EG(class_table), lc_trait_name); - zend_string_release_ex(lc_trait_name, 0); + exclude_ce = zend_hash_find_ptr_lc(EG(class_table), class_name); if (!exclude_ce || !(exclude_ce->ce_flags & ZEND_ACC_LINKED)) { zend_error_noreturn(E_COMPILE_ERROR, "Could not find trait %s", ZSTR_VAL(class_name)); } @@ -2619,9 +2610,7 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_e lcname = zend_string_tolower(cur_method_ref->method_name); if (cur_method_ref->class_name) { /* For all aliases with an explicit class name, resolve the class now. */ - lc_trait_name = zend_string_tolower(cur_method_ref->class_name); - trait = zend_hash_find_ptr(EG(class_table), lc_trait_name); - zend_string_release_ex(lc_trait_name, 0); + trait = zend_hash_find_ptr_lc(EG(class_table), cur_method_ref->class_name); if (!trait || !(trait->ce_flags & ZEND_ACC_LINKED)) { zend_error_noreturn(E_COMPILE_ERROR, "Could not find trait %s", ZSTR_VAL(cur_method_ref->class_name)); } diff --git a/ext/hash/hash.c b/ext/hash/hash.c index e2af98c81ddf..74abf02acd81 100644 --- a/ext/hash/hash.c +++ b/ext/hash/hash.c @@ -102,9 +102,7 @@ static struct mhash_bc_entry mhash_to_hash[MHASH_NUM_ALGOS] = { PHP_HASH_API const php_hash_ops *php_hash_fetch_ops(zend_string *algo) /* {{{ */ { - zend_string *lower = zend_string_tolower(algo); - const php_hash_ops *ops = zend_hash_find_ptr(&php_hash_hashtable, lower); - zend_string_release(lower); + const php_hash_ops *ops = zend_hash_find_ptr_lc(&php_hash_hashtable, algo); return ops; } diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 83e933b01181..6f606d9d37d5 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3847,9 +3847,7 @@ static zend_result preload_resolve_deps(preload_error *error, const zend_class_e memset(error, 0, sizeof(preload_error)); if (ce->parent_name) { - zend_string *key = zend_string_tolower(ce->parent_name); - const zend_class_entry *parent = zend_hash_find_ptr(EG(class_table), key); - zend_string_release(key); + const zend_class_entry *parent = zend_hash_find_ptr_lc(EG(class_table), ce->parent_name); if (!parent) { error->kind = "Unknown parent "; error->name = ZSTR_VAL(ce->parent_name); diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c index 6a47ec30c862..dcd9f7b126df 100644 --- a/ext/pdo/pdo_dbh.c +++ b/ext/pdo/pdo_dbh.c @@ -1484,7 +1484,6 @@ static zend_function *dbh_method_get(zend_object **object, zend_string *method_n { zend_function *fbc = NULL; pdo_dbh_object_t *dbh_obj = php_pdo_dbh_fetch_object(*object); - zend_string *lc_method_name; if ((fbc = zend_std_get_method(object, method_name, key)) == NULL) { /* not a pre-defined method, nor a user-defined method; check @@ -1497,9 +1496,7 @@ static zend_function *dbh_method_get(zend_object **object, zend_string *method_n } } - lc_method_name = zend_string_tolower(method_name); - fbc = zend_hash_find_ptr(dbh_obj->inner->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_DBH], lc_method_name); - zend_string_release_ex(lc_method_name, 0); + fbc = zend_hash_find_ptr_lc(dbh_obj->inner->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_DBH], method_name); } out: diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index a9f6b579977e..37c45cb02168 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -859,7 +859,6 @@ static void _function_string(smart_str *str, const zend_function *fptr, const ze { smart_str param_indent = {0}; zend_function *overwrites; - zend_string *lc_name; /* TBD: Repair indenting of doc comment (or is this to be done in the parser?) * What's "wrong" is that any whitespace before the doc comment start is @@ -885,13 +884,11 @@ static void _function_string(smart_str *str, const zend_function *fptr, const ze if (fptr->common.scope != scope) { smart_str_append_printf(str, ", inherits %s", ZSTR_VAL(fptr->common.scope->name)); } else if (fptr->common.scope->parent) { - lc_name = zend_string_tolower(fptr->common.function_name); - if ((overwrites = zend_hash_find_ptr(&fptr->common.scope->parent->function_table, lc_name)) != NULL) { + if ((overwrites = zend_hash_find_ptr_lc(&fptr->common.scope->parent->function_table, fptr->common.function_name)) != NULL) { if (fptr->common.scope != overwrites->common.scope && !(overwrites->common.fn_flags & ZEND_ACC_PRIVATE)) { smart_str_append_printf(str, ", overwrites %s", ZSTR_VAL(overwrites->common.scope->name)); } } - zend_string_release_ex(lc_name, 0); } } if (fptr->common.prototype && fptr->common.prototype->common.scope) { diff --git a/sapi/cli/php_cli.c b/sapi/cli/php_cli.c index 9a05f8e68547..e5e573d1533c 100644 --- a/sapi/cli/php_cli.c +++ b/sapi/cli/php_cli.c @@ -1093,10 +1093,9 @@ static int do_cli(int argc, char **argv) /* {{{ */ case PHP_CLI_MODE_REFLECTION_EXT_INFO: { size_t len = strlen(reflection_what); - char *lcname = zend_str_tolower_dup(reflection_what, len); zend_module_entry *module; - if ((module = zend_hash_str_find_ptr(&module_registry, lcname, len)) == NULL) { + if ((module = zend_hash_str_find_ptr_lc(&module_registry, reflection_what, len)) == NULL) { if (!strcmp(reflection_what, "main")) { display_ini_entries(NULL); } else { @@ -1107,7 +1106,6 @@ static int do_cli(int argc, char **argv) /* {{{ */ php_info_print_module(module); } - efree(lcname); break; } diff --git a/sapi/phpdbg/phpdbg_bp.c b/sapi/phpdbg/phpdbg_bp.c index 4dfa89d4b0fe..b9c3436280cf 100644 --- a/sapi/phpdbg/phpdbg_bp.c +++ b/sapi/phpdbg/phpdbg_bp.c @@ -966,13 +966,7 @@ static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_symbol(zend_function *f } if (ops->function_name) { - phpdbg_breakbase_t *brake; - zend_string *fname = zend_string_tolower(ops->function_name); - - brake = zend_hash_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM], fname); - - zend_string_release(fname); - return brake; + return zend_hash_find_ptr_lc(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM], ops->function_name); } else { return zend_hash_str_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM], ZEND_STRL("main")); } @@ -982,17 +976,11 @@ static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_method(zend_op_array *o { HashTable *class_table; phpdbg_breakbase_t *brake = NULL; - zend_string *class_lcname = zend_string_tolower(ops->scope->name); - - if ((class_table = zend_hash_find_ptr(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD], class_lcname))) { - zend_string *lcname = zend_string_tolower(ops->function_name); - - brake = zend_hash_find_ptr(class_table, lcname); - zend_string_release(lcname); + if ((class_table = zend_hash_find_ptr_lc(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD], ops->scope->name))) { + brake = zend_hash_find_ptr_lc(class_table, ops->function_name); } - zend_string_release(class_lcname); return brake; } /* }}} */ diff --git a/sapi/phpdbg/phpdbg_list.c b/sapi/phpdbg/phpdbg_list.c index 505b04e81f91..3931fc9c1525 100644 --- a/sapi/phpdbg/phpdbg_list.c +++ b/sapi/phpdbg/phpdbg_list.c @@ -88,15 +88,12 @@ PHPDBG_LIST(method) /* {{{ */ if (phpdbg_safe_class_lookup(param->method.class, strlen(param->method.class), &ce) == SUCCESS) { zend_function *function; - char *lcname = zend_str_tolower_dup(param->method.name, strlen(param->method.name)); - if ((function = zend_hash_str_find_ptr(&ce->function_table, lcname, strlen(lcname)))) { + if ((function = zend_hash_str_find_ptr_lc(&ce->function_table, param->method.name, strlen(param->method.name)))) { phpdbg_list_function(function); } else { phpdbg_error("Could not find %s::%s", param->method.class, param->method.name); } - - efree(lcname); } else { phpdbg_error("Could not find the class %s", param->method.class); } From b6938a25ee7dcce21f5386668e4a8ba58f2330a6 Mon Sep 17 00:00:00 2001 From: Jorg Sowa Date: Thu, 2 Jul 2026 20:11:28 +0200 Subject: [PATCH 4/4] phpdbg: fix leaked lowercased lookup keys in phpdbg_resolve_opline_break phpdbg_resolve_opline_break() passed the result of zend_str_tolower_dup() directly as the lookup key to zend_hash_str_find_ptr() for both the class and the function/method lookups. That emalloc'd buffer was never freed, leaking on every method/function opline breakpoint resolution. Use zend_hash_str_find_ptr_lc(), which lowercases into a (stack-allocated for short names) temporary and frees it internally, instead of duplicating the key by hand and leaking it. Add a regression test that exercises all three paths through the two lookups (class + method found, method not found, class not found). Closes GH-22563 --- NEWS | 2 ++ sapi/phpdbg/phpdbg_bp.c | 4 +-- .../phpdbg_resolve_opline_break_leak.phpt | 26 +++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 sapi/phpdbg/tests/phpdbg_resolve_opline_break_leak.phpt diff --git a/NEWS b/NEWS index 6ac3e8db3939..1edc25b4899d 100644 --- a/NEWS +++ b/NEWS @@ -38,6 +38,8 @@ PHP NEWS - PHPDBG: . Fixed bug GH-17387 (Trivial crash in phpdbg lexer). (iliaal) + . Fixed fleaked lowercased lookup keys in phpdbg_resolve_opline_break. + (jorgsowa) - Reflection: . Fixed bug GH-22324 (Ignore leading namespace separator in diff --git a/sapi/phpdbg/phpdbg_bp.c b/sapi/phpdbg/phpdbg_bp.c index 90f8ed7bb129..361e4635f1f9 100644 --- a/sapi/phpdbg/phpdbg_bp.c +++ b/sapi/phpdbg/phpdbg_bp.c @@ -604,13 +604,13 @@ PHPDBG_API int phpdbg_resolve_opline_break(phpdbg_breakopline_t *new_break) /* { if (new_break->class_name != NULL) { zend_class_entry *ce; - if (!(ce = zend_hash_str_find_ptr(EG(class_table), zend_str_tolower_dup(new_break->class_name, new_break->class_len), new_break->class_len))) { + if (!(ce = zend_hash_str_find_ptr_lc(EG(class_table), new_break->class_name, new_break->class_len))) { return FAILURE; } func_table = &ce->function_table; } - if (!(func = zend_hash_str_find_ptr(func_table, zend_str_tolower_dup(new_break->func_name, new_break->func_len), new_break->func_len))) { + if (!(func = zend_hash_str_find_ptr_lc(func_table, new_break->func_name, new_break->func_len))) { if (new_break->class_name != NULL && new_break->func_name != NULL) { phpdbg_error("Method %s doesn't exist in class %s", new_break->func_name, new_break->class_name); return 2; diff --git a/sapi/phpdbg/tests/phpdbg_resolve_opline_break_leak.phpt b/sapi/phpdbg/tests/phpdbg_resolve_opline_break_leak.phpt new file mode 100644 index 000000000000..411697cee021 --- /dev/null +++ b/sapi/phpdbg/tests/phpdbg_resolve_opline_break_leak.phpt @@ -0,0 +1,26 @@ +--TEST-- +Resolving method/function opline breakpoints must not leak the lookup keys +--PHPDBG-- +b Foo::bar#0 +b baz#0 +b Foo::nope#0 +b Nope::bar#0 +q +--EXPECTF-- +[Successful compilation of %s] +prompt> [Breakpoint #0 added at Foo::bar#0] +prompt> [Breakpoint #1 added at baz#0] +prompt> [Method nope doesn't exist in class Foo] +prompt> [Pending breakpoint #3 at Nope::bar#0] +prompt> +--FILE-- +