Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions ext/pcre/php_pcre.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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;
}
/* }}} */

Expand Down
58 changes: 58 additions & 0 deletions ext/pcre/tests/pcre_reentrancy.phpt
Original file line number Diff line number Diff line change
@@ -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"
Loading