From 630dff8373da26f1e0e9a31df5de4a9ff708338f Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 25 Feb 2026 06:13:06 +0000 Subject: [PATCH 1/2] ext/pcre: fix mdata_used race conditions in PCRE functions Mirror the mdata_used protection pattern from php_pcre_replace_func_impl in php_pcre_match_impl, php_pcre_replace_impl, php_pcre_split_impl, and php_pcre_grep_impl. --- ext/pcre/php_pcre.c | 28 ++++++++++++-- ext/pcre/tests/pcre_reentrancy.phpt | 58 +++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 ext/pcre/tests/pcre_reentrancy.phpt diff --git a/ext/pcre/php_pcre.c b/ext/pcre/php_pcre.c index 24931466199c..f5b80045e099 100644 --- a/ext/pcre/php_pcre.c +++ b/ext/pcre/php_pcre.c @@ -1173,6 +1173,7 @@ PHPAPI void php_pcre_match_impl(pcre_cache_entry *pce, zend_string *subject_str, HashTable *marks = NULL; /* Array of marks for PREG_PATTERN_ORDER */ pcre2_match_data *match_data; PCRE2_SIZE start_offset2, orig_start_offset; + bool old_mdata_used; char *subject = ZSTR_VAL(subject_str); size_t subject_len = ZSTR_LEN(subject_str); @@ -1242,12 +1243,15 @@ PHPAPI void php_pcre_match_impl(pcre_cache_entry *pce, zend_string *subject_str, matched = 0; PCRE_G(error_code) = PHP_PCRE_NO_ERROR; - if (!mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) { + old_mdata_used = mdata_used; + if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) { + mdata_used = 1; match_data = mdata; } else { match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm)); if (!match_data) { PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR; + mdata_used = old_mdata_used; RETURN_FALSE; } } @@ -1428,6 +1432,7 @@ PHPAPI void php_pcre_match_impl(pcre_cache_entry *pce, zend_string *subject_str, if (match_data != mdata) { pcre2_match_data_free(match_data); } + mdata_used = old_mdata_used; /* Add the match sets to the output array and clean up */ if (match_sets) { @@ -1632,6 +1637,7 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su size_t result_len; /* Length of result */ zend_string *result; /* Result of replacement */ pcre2_match_data *match_data; + bool old_mdata_used; /* Calculate the size of the offsets array, and allocate memory for it. */ num_subpats = pce->capture_count + 1; @@ -1645,12 +1651,15 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su result_len = 0; PCRE_G(error_code) = PHP_PCRE_NO_ERROR; - if (!mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) { + old_mdata_used = mdata_used; + if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) { + mdata_used = 1; match_data = mdata; } else { match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm)); if (!match_data) { PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR; + mdata_used = old_mdata_used; return NULL; } } @@ -1847,6 +1856,7 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su if (match_data != mdata) { pcre2_match_data_free(match_data); } + mdata_used = old_mdata_used; return result; } @@ -2575,6 +2585,7 @@ PHPAPI void php_pcre_split_impl(pcre_cache_entry *pce, zend_string *subject_str, uint32_t num_subpats; /* Number of captured subpatterns */ zval tmp; pcre2_match_data *match_data; + bool old_mdata_used; char *subject = ZSTR_VAL(subject_str); no_empty = flags & PREG_SPLIT_NO_EMPTY; @@ -2601,12 +2612,15 @@ PHPAPI void php_pcre_split_impl(pcre_cache_entry *pce, zend_string *subject_str, goto last; } - if (!mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) { + old_mdata_used = mdata_used; + if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) { + mdata_used = 1; match_data = mdata; } else { match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm)); if (!match_data) { PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR; + mdata_used = old_mdata_used; zval_ptr_dtor(return_value); RETURN_FALSE; } @@ -2730,6 +2744,7 @@ PHPAPI void php_pcre_split_impl(pcre_cache_entry *pce, zend_string *subject_str, if (match_data != mdata) { pcre2_match_data_free(match_data); } + mdata_used = old_mdata_used; if (PCRE_G(error_code) != PHP_PCRE_NO_ERROR) { zval_ptr_dtor(return_value); @@ -2929,6 +2944,7 @@ PHPAPI void php_pcre_grep_impl(pcre_cache_entry *pce, zval *input, zval *return zend_ulong num_key; bool invert; /* Whether to return non-matching entries */ + bool old_mdata_used; pcre2_match_data *match_data; invert = flags & PREG_GREP_INVERT ? 1 : 0; @@ -2941,12 +2957,15 @@ PHPAPI void php_pcre_grep_impl(pcre_cache_entry *pce, zval *input, zval *return PCRE_G(error_code) = PHP_PCRE_NO_ERROR; - if (!mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) { + old_mdata_used = mdata_used; + if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) { + mdata_used = 1; match_data = mdata; } else { match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm)); if (!match_data) { PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR; + mdata_used = old_mdata_used; return; } } @@ -3006,6 +3025,7 @@ PHPAPI void php_pcre_grep_impl(pcre_cache_entry *pce, zval *input, zval *return if (match_data != mdata) { pcre2_match_data_free(match_data); } + mdata_used = old_mdata_used; } /* }}} */ diff --git a/ext/pcre/tests/pcre_reentrancy.phpt b/ext/pcre/tests/pcre_reentrancy.phpt new file mode 100644 index 000000000000..5fe4071e4fe1 --- /dev/null +++ b/ext/pcre/tests/pcre_reentrancy.phpt @@ -0,0 +1,58 @@ +--TEST-- +PCRE re-entrancy: nested calls should not corrupt global match data +--EXTENSIONS-- +pcre +--FILE-- + +--EXPECT-- +Testing nested PCRE calls... +Outer match: a +Outer match: b +Outer match: c +string(3) "ABC" + +Testing deep nesting... +string(7) "SUCCESS" From 8f78498892610152db8934daea691b890fc272d7 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Thu, 26 Feb 2026 20:00:22 +0000 Subject: [PATCH 2/2] feedback --- ext/pcre/php_pcre.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/ext/pcre/php_pcre.c b/ext/pcre/php_pcre.c index f5b80045e099..47dafea202bd 100644 --- a/ext/pcre/php_pcre.c +++ b/ext/pcre/php_pcre.c @@ -1245,13 +1245,12 @@ PHPAPI void php_pcre_match_impl(pcre_cache_entry *pce, zend_string *subject_str, old_mdata_used = mdata_used; if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) { - mdata_used = 1; + mdata_used = true; match_data = mdata; } else { match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm)); if (!match_data) { PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR; - mdata_used = old_mdata_used; RETURN_FALSE; } } @@ -1653,13 +1652,12 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su old_mdata_used = mdata_used; if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) { - mdata_used = 1; + mdata_used = true; match_data = mdata; } else { match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm)); if (!match_data) { PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR; - mdata_used = old_mdata_used; return NULL; } } @@ -2614,13 +2612,12 @@ PHPAPI void php_pcre_split_impl(pcre_cache_entry *pce, zend_string *subject_str, old_mdata_used = mdata_used; if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) { - mdata_used = 1; + mdata_used = true; match_data = mdata; } else { match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm)); if (!match_data) { PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR; - mdata_used = old_mdata_used; zval_ptr_dtor(return_value); RETURN_FALSE; } @@ -2959,13 +2956,12 @@ PHPAPI void php_pcre_grep_impl(pcre_cache_entry *pce, zval *input, zval *return old_mdata_used = mdata_used; if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) { - mdata_used = 1; + mdata_used = true; match_data = mdata; } else { match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm)); if (!match_data) { PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR; - mdata_used = old_mdata_used; return; } }