Skip to content

Commit 7a8a863

Browse files
committed
Merge branch 'PHP-8.4' into PHP-8.5
* PHP-8.4: ext/pcre: fix mdata_used race conditions in PCRE functions
2 parents 27e12b5 + f8114f5 commit 7a8a863

File tree

3 files changed

+82
-4
lines changed

3 files changed

+82
-4
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ PHP NEWS
66
. Fixed bug GH-21052 (Preloaded constant erroneously propagated to file-cached
77
script). (ilutov)
88

9+
- PCRE:
10+
. Fixed re-entrancy issue on php_pcre_match_impl, php_pcre_replace_impl,
11+
php_pcre_split_impl, and php_pcre_grep_impl. (David Carlier)
12+
913
- Standard:
1014
. Fixed bug GH-20906 (Assertion failure when messing up output buffers).
1115
(ndossche)

ext/pcre/php_pcre.c

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1167,6 +1167,7 @@ PHPAPI void php_pcre_match_impl(pcre_cache_entry *pce, zend_string *subject_str,
11671167
HashTable *marks = NULL; /* Array of marks for PREG_PATTERN_ORDER */
11681168
pcre2_match_data *match_data;
11691169
PCRE2_SIZE start_offset2, orig_start_offset;
1170+
bool old_mdata_used;
11701171

11711172
char *subject = ZSTR_VAL(subject_str);
11721173
size_t subject_len = ZSTR_LEN(subject_str);
@@ -1236,7 +1237,9 @@ PHPAPI void php_pcre_match_impl(pcre_cache_entry *pce, zend_string *subject_str,
12361237
matched = 0;
12371238
PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
12381239

1239-
if (!mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
1240+
old_mdata_used = mdata_used;
1241+
if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
1242+
mdata_used = true;
12401243
match_data = mdata;
12411244
} else {
12421245
match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
@@ -1433,6 +1436,7 @@ PHPAPI void php_pcre_match_impl(pcre_cache_entry *pce, zend_string *subject_str,
14331436
if (match_data != mdata) {
14341437
pcre2_match_data_free(match_data);
14351438
}
1439+
mdata_used = old_mdata_used;
14361440

14371441
/* Add the match sets to the output array and clean up */
14381442
if (match_sets) {
@@ -1629,6 +1633,7 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su
16291633
size_t result_len; /* Length of result */
16301634
zend_string *result; /* Result of replacement */
16311635
pcre2_match_data *match_data;
1636+
bool old_mdata_used;
16321637

16331638
/* Calculate the size of the offsets array, and allocate memory for it. */
16341639
num_subpats = pce->capture_count + 1;
@@ -1642,7 +1647,9 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su
16421647
result_len = 0;
16431648
PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
16441649

1645-
if (!mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
1650+
old_mdata_used = mdata_used;
1651+
if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
1652+
mdata_used = true;
16461653
match_data = mdata;
16471654
} else {
16481655
match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
@@ -1844,6 +1851,7 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su
18441851
if (match_data != mdata) {
18451852
pcre2_match_data_free(match_data);
18461853
}
1854+
mdata_used = old_mdata_used;
18471855

18481856
return result;
18491857
}
@@ -2569,6 +2577,7 @@ PHPAPI void php_pcre_split_impl(pcre_cache_entry *pce, zend_string *subject_str,
25692577
uint32_t num_subpats; /* Number of captured subpatterns */
25702578
zval tmp;
25712579
pcre2_match_data *match_data;
2580+
bool old_mdata_used;
25722581
char *subject = ZSTR_VAL(subject_str);
25732582

25742583
no_empty = flags & PREG_SPLIT_NO_EMPTY;
@@ -2595,7 +2604,9 @@ PHPAPI void php_pcre_split_impl(pcre_cache_entry *pce, zend_string *subject_str,
25952604
goto last;
25962605
}
25972606

2598-
if (!mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
2607+
old_mdata_used = mdata_used;
2608+
if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
2609+
mdata_used = true;
25992610
match_data = mdata;
26002611
} else {
26012612
match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
@@ -2724,6 +2735,7 @@ PHPAPI void php_pcre_split_impl(pcre_cache_entry *pce, zend_string *subject_str,
27242735
if (match_data != mdata) {
27252736
pcre2_match_data_free(match_data);
27262737
}
2738+
mdata_used = old_mdata_used;
27272739

27282740
if (PCRE_G(error_code) != PHP_PCRE_NO_ERROR) {
27292741
zval_ptr_dtor(return_value);
@@ -2923,6 +2935,7 @@ PHPAPI void php_pcre_grep_impl(pcre_cache_entry *pce, zval *input, zval *return
29232935
zend_ulong num_key;
29242936
bool invert; /* Whether to return non-matching
29252937
entries */
2938+
bool old_mdata_used;
29262939
pcre2_match_data *match_data;
29272940
invert = flags & PREG_GREP_INVERT ? 1 : 0;
29282941

@@ -2935,7 +2948,9 @@ PHPAPI void php_pcre_grep_impl(pcre_cache_entry *pce, zval *input, zval *return
29352948

29362949
PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
29372950

2938-
if (!mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
2951+
old_mdata_used = mdata_used;
2952+
if (!old_mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
2953+
mdata_used = true;
29392954
match_data = mdata;
29402955
} else {
29412956
match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
@@ -3000,6 +3015,7 @@ PHPAPI void php_pcre_grep_impl(pcre_cache_entry *pce, zval *input, zval *return
30003015
if (match_data != mdata) {
30013016
pcre2_match_data_free(match_data);
30023017
}
3018+
mdata_used = old_mdata_used;
30033019
}
30043020
/* }}} */
30053021

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
--TEST--
2+
PCRE re-entrancy: nested calls should not corrupt global match data
3+
--EXTENSIONS--
4+
pcre
5+
--FILE--
6+
<?php
7+
8+
echo "Testing nested PCRE calls..." . PHP_EOL;
9+
10+
$subject = 'abc';
11+
12+
// preg_replace_callback is the most common way to trigger re-entrancy
13+
$result = preg_replace_callback('/./', function($m) {
14+
$char = $m[0];
15+
echo "Outer match: $char" . PHP_EOL;
16+
17+
// 1. Nested preg_match
18+
preg_match('/./', 'inner', $inner_m);
19+
20+
// 2. Nested preg_replace (string version)
21+
preg_replace('/n/', 'N', 'inner');
22+
23+
// 3. Nested preg_split
24+
preg_split('/n/', 'inner');
25+
26+
// 4. Nested preg_grep
27+
preg_grep('/n/', ['inner']);
28+
29+
// If any of the above stole the global mdata buffer without setting mdata_used,
30+
// the 'offsets' used by this outer preg_replace_callback loop would be corrupted.
31+
32+
return strtoupper($char);
33+
}, $subject);
34+
35+
var_dump($result);
36+
37+
echo PHP_EOL . "Testing deep nesting..." . PHP_EOL;
38+
39+
$result = preg_replace_callback('/a/', function($m) {
40+
return preg_replace_callback('/b/', function($m) {
41+
return preg_replace_callback('/c/', function($m) {
42+
return "SUCCESS";
43+
}, 'c');
44+
}, 'b');
45+
}, 'a');
46+
47+
var_dump($result);
48+
49+
?>
50+
--EXPECT--
51+
Testing nested PCRE calls...
52+
Outer match: a
53+
Outer match: b
54+
Outer match: c
55+
string(3) "ABC"
56+
57+
Testing deep nesting...
58+
string(7) "SUCCESS"

0 commit comments

Comments
 (0)