From 713f66225655e190d68d326841977c578a7a06ce Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 25 Feb 2026 20:34:00 +0000 Subject: [PATCH] ext/pcre: fix memory leaks on error paths Fix pcre2_code leak when pcre2_pattern_info() fails after a successful pcre2_compile(), and fix match_sets/match_data/marks leak when offsets[1] < offsets[0] in php_pcre_match_impl(). --- ext/pcre/php_pcre.c | 15 ++++++++++++++- .../preg_match_all_negative_length_match.phpt | 10 ++++++++++ .../tests/preg_match_negative_length_match.phpt | 10 ++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 ext/pcre/tests/preg_match_all_negative_length_match.phpt create mode 100644 ext/pcre/tests/preg_match_negative_length_match.phpt diff --git a/ext/pcre/php_pcre.c b/ext/pcre/php_pcre.c index 24931466199c..ff53380afaeb 100644 --- a/ext/pcre/php_pcre.c +++ b/ext/pcre/php_pcre.c @@ -840,6 +840,7 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo if (key != regex) { zend_string_release_ex(key, 0); } + pcre2_code_free(new_entry.re); php_error_docref(NULL, E_WARNING, "Internal pcre2_pattern_info() error %d", rc); pcre_handle_exec_error(PCRE2_ERROR_INTERNAL); return NULL; @@ -850,6 +851,7 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo if (key != regex) { zend_string_release_ex(key, 0); } + pcre2_code_free(new_entry.re); php_error_docref(NULL, E_WARNING, "Internal pcre_pattern_info() error %d", rc); pcre_handle_exec_error(PCRE2_ERROR_INTERNAL); return NULL; @@ -1294,7 +1296,18 @@ PHPAPI void php_pcre_match_impl(pcre_cache_entry *pce, zend_string *subject_str, if (subpats != NULL) { /* Try to get the list of substrings and display a warning if failed. */ if (UNEXPECTED(offsets[1] < offsets[0])) { - if (match_sets) efree(match_sets); + if (match_sets) { + for (i = 0; i < num_subpats; i++) { + zend_array_destroy(match_sets[i]); + } + efree(match_sets); + } + if (marks) { + zend_array_destroy(marks); + } + if (match_data != mdata) { + pcre2_match_data_free(match_data); + } php_error_docref(NULL, E_WARNING, "Get subpatterns list failed"); RETURN_FALSE; } diff --git a/ext/pcre/tests/preg_match_all_negative_length_match.phpt b/ext/pcre/tests/preg_match_all_negative_length_match.phpt new file mode 100644 index 000000000000..0deb27749e19 --- /dev/null +++ b/ext/pcre/tests/preg_match_all_negative_length_match.phpt @@ -0,0 +1,10 @@ +--TEST-- +preg_match_all() resource cleanup when \K in lookahead causes negative-length match +--FILE-- + +--EXPECTF-- +Warning: preg_match_all(): Get subpatterns list failed in %s on line %d +bool(false) diff --git a/ext/pcre/tests/preg_match_negative_length_match.phpt b/ext/pcre/tests/preg_match_negative_length_match.phpt new file mode 100644 index 000000000000..f321cb20b9c7 --- /dev/null +++ b/ext/pcre/tests/preg_match_negative_length_match.phpt @@ -0,0 +1,10 @@ +--TEST-- +preg_match() resource cleanup when \K in lookahead causes negative-length match +--FILE-- + +--EXPECTF-- +Warning: preg_match(): Get subpatterns list failed in %s on line %d +bool(false)