mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
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. close GH-21291
This commit is contained in:
4
NEWS
4
NEWS
@@ -2,6 +2,10 @@ PHP NEWS
|
||||
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||
?? ??? ????, PHP 8.4.20
|
||||
|
||||
- PCRE:
|
||||
. Fixed re-entrancy issue on php_pcre_match_impl, php_pcre_replace_impl,
|
||||
php_pcre_split_impl, and php_pcre_grep_impl. (David Carlier)
|
||||
|
||||
- Standard:
|
||||
. Fixed bug GH-20906 (Assertion failure when messing up output buffers).
|
||||
(ndossche)
|
||||
|
||||
@@ -1175,6 +1175,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);
|
||||
@@ -1244,7 +1245,9 @@ 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 = true;
|
||||
match_data = mdata;
|
||||
} else {
|
||||
match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
|
||||
@@ -1441,6 +1444,7 @@ error:
|
||||
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) {
|
||||
@@ -1645,6 +1649,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;
|
||||
@@ -1658,7 +1663,9 @@ 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 = true;
|
||||
match_data = mdata;
|
||||
} else {
|
||||
match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
|
||||
@@ -1860,6 +1867,7 @@ error:
|
||||
if (match_data != mdata) {
|
||||
pcre2_match_data_free(match_data);
|
||||
}
|
||||
mdata_used = old_mdata_used;
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -2588,6 +2596,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;
|
||||
@@ -2614,7 +2623,9 @@ 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 = true;
|
||||
match_data = mdata;
|
||||
} else {
|
||||
match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
|
||||
@@ -2743,6 +2754,7 @@ error:
|
||||
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);
|
||||
@@ -2942,6 +2954,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;
|
||||
|
||||
@@ -2954,7 +2967,9 @@ 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 = true;
|
||||
match_data = mdata;
|
||||
} else {
|
||||
match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
|
||||
@@ -3019,6 +3034,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;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
|
||||
58
ext/pcre/tests/pcre_reentrancy.phpt
Normal file
58
ext/pcre/tests/pcre_reentrancy.phpt
Normal file
@@ -0,0 +1,58 @@
|
||||
--TEST--
|
||||
PCRE re-entrancy: nested calls should not corrupt global match data
|
||||
--EXTENSIONS--
|
||||
pcre
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
echo "Testing nested PCRE calls..." . PHP_EOL;
|
||||
|
||||
$subject = 'abc';
|
||||
|
||||
// preg_replace_callback is the most common way to trigger re-entrancy
|
||||
$result = preg_replace_callback('/./', function($m) {
|
||||
$char = $m[0];
|
||||
echo "Outer match: $char" . PHP_EOL;
|
||||
|
||||
// 1. Nested preg_match
|
||||
preg_match('/./', 'inner', $inner_m);
|
||||
|
||||
// 2. Nested preg_replace (string version)
|
||||
preg_replace('/n/', 'N', 'inner');
|
||||
|
||||
// 3. Nested preg_split
|
||||
preg_split('/n/', 'inner');
|
||||
|
||||
// 4. Nested preg_grep
|
||||
preg_grep('/n/', ['inner']);
|
||||
|
||||
// If any of the above stole the global mdata buffer without setting mdata_used,
|
||||
// the 'offsets' used by this outer preg_replace_callback loop would be corrupted.
|
||||
|
||||
return strtoupper($char);
|
||||
}, $subject);
|
||||
|
||||
var_dump($result);
|
||||
|
||||
echo PHP_EOL . "Testing deep nesting..." . PHP_EOL;
|
||||
|
||||
$result = preg_replace_callback('/a/', function($m) {
|
||||
return preg_replace_callback('/b/', function($m) {
|
||||
return preg_replace_callback('/c/', function($m) {
|
||||
return "SUCCESS";
|
||||
}, 'c');
|
||||
}, 'b');
|
||||
}, 'a');
|
||||
|
||||
var_dump($result);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Testing nested PCRE calls...
|
||||
Outer match: a
|
||||
Outer match: b
|
||||
Outer match: c
|
||||
string(3) "ABC"
|
||||
|
||||
Testing deep nesting...
|
||||
string(7) "SUCCESS"
|
||||
Reference in New Issue
Block a user