Commit 78702fa4701 for php.net
commit 78702fa470119e02df9323f8e18b8320ff55ded4
Author: David Carlier <devnexen@gmail.com>
Date: Wed Feb 25 20:34:00 2026 +0000
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().
close GH-21298
diff --git a/NEWS b/NEWS
index 6fba1e4e64a..1a4b588aa1e 100644
--- a/NEWS
+++ b/NEWS
@@ -60,6 +60,9 @@ PHP NEWS
- PCRE:
. Fixed preg_match memory leak with invalid regexes. (David Carlier)
+ . Fixed pcre2_code leak when pcre2_pattern_info() fails after a
+ successful pcre2_compile(), and match_sets/match_data/marks leaks
+ in php_pcre_match_impl(). (David Carlier)
- PDO_PGSQL:
. Fixed bug GH-21055 (connection attribute status typo for GSS negotiation).
diff --git a/ext/pcre/php_pcre.c b/ext/pcre/php_pcre.c
index 24931466199..ff53380afae 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 00000000000..0deb27749e1
--- /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--
+<?php
+$result = preg_match_all('/(?=ab\K)a/', 'ab', $matches);
+var_dump($result);
+?>
+--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 00000000000..f321cb20b9c
--- /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--
+<?php
+$result = preg_match('/(?=ab\K)a/', 'ab', $matches);
+var_dump($result);
+?>
+--EXPECTF--
+Warning: preg_match(): Get subpatterns list failed in %s on line %d
+bool(false)