summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS7
-rw-r--r--ext/pcre/php_pcre.c34
-rw-r--r--ext/pcre/tests/bug79188.phpt13
3 files changed, 36 insertions, 18 deletions
diff --git a/NEWS b/NEWS
index 90ac80eb5e..b24e85e554 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,12 @@
PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+?? ??? ????, PHP 7.4.4
+
+- PCRE:
+ . Fixed bug #79188 (Memory corruption in preg_replace/preg_replace_callback
+ and unicode). (Nikita)
+
?? ??? ????, PHP 7.4.3
- Core:
diff --git a/ext/pcre/php_pcre.c b/ext/pcre/php_pcre.c
index 15afc29778..481d564f66 100644
--- a/ext/pcre/php_pcre.c
+++ b/ext/pcre/php_pcre.c
@@ -1582,6 +1582,7 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su
size_t match_len; /* Length of the current match */
int backref; /* Backreference number */
PCRE2_SIZE start_offset; /* Where the new search starts */
+ size_t last_end_offset; /* Where the last search ended */
char *walkbuf, /* Location of current replacement in the result */
*walk, /* Used to walk the replacement string */
*match, /* The current match */
@@ -1600,6 +1601,7 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su
/* Initialize */
match = NULL;
start_offset = 0;
+ last_end_offset = 0;
result_len = 0;
PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
@@ -1626,7 +1628,7 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su
options, match_data, mctx);
while (1) {
- piece = subject + start_offset;
+ piece = subject + last_end_offset;
if (count >= 0 && limit > 0) {
zend_bool simple_string;
@@ -1656,7 +1658,7 @@ matched:
/* Set the match location in subject */
match = subject + offsets[0];
- new_len = result_len + offsets[0] - start_offset; /* part before the match */
+ new_len = result_len + offsets[0] - last_end_offset; /* part before the match */
walk = ZSTR_VAL(replace_str);
replace_end = walk + ZSTR_LEN(replace_str);
@@ -1733,7 +1735,7 @@ matched:
limit--;
/* Advance to the next piece. */
- start_offset = offsets[1];
+ start_offset = last_end_offset = offsets[1];
/* If we have matched an empty string, mimic what Perl's /g options does.
This turns out to be rather cunning. First we set PCRE2_NOTEMPTY_ATSTART and try
@@ -1753,10 +1755,7 @@ matched:
to achieve this, unless we're already at the end of the string. */
if (start_offset < subject_len) {
size_t unit_len = calculate_unit_length(pce, piece);
-
start_offset += unit_len;
- memcpy(ZSTR_VAL(result) + result_len, piece, unit_len);
- result_len += unit_len;
} else {
goto not_matched;
}
@@ -1771,7 +1770,7 @@ not_matched:
result = zend_string_copy(subject_str);
break;
}
- new_len = result_len + subject_len - start_offset;
+ new_len = result_len + subject_len - last_end_offset;
if (new_len >= alloc_len) {
alloc_len = new_len; /* now we know exactly how long it is */
if (NULL != result) {
@@ -1781,8 +1780,8 @@ not_matched:
}
}
/* stick that last bit of string on our output */
- memcpy(ZSTR_VAL(result) + result_len, piece, subject_len - start_offset);
- result_len += subject_len - start_offset;
+ memcpy(ZSTR_VAL(result) + result_len, piece, subject_len - last_end_offset);
+ result_len += subject_len - last_end_offset;
ZSTR_VAL(result)[result_len] = '\0';
ZSTR_LEN(result) = result_len;
break;
@@ -1824,6 +1823,7 @@ static zend_string *php_pcre_replace_func_impl(pcre_cache_entry *pce, zend_strin
size_t new_len; /* Length of needed storage */
size_t alloc_len; /* Actual allocated length */
PCRE2_SIZE start_offset; /* Where the new search starts */
+ size_t last_end_offset; /* Where the last search ended */
char *match, /* The current match */
*piece; /* The current piece of subject */
size_t result_len; /* Length of result */
@@ -1853,6 +1853,7 @@ static zend_string *php_pcre_replace_func_impl(pcre_cache_entry *pce, zend_strin
/* Initialize */
match = NULL;
start_offset = 0;
+ last_end_offset = 0;
result_len = 0;
PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
@@ -1885,7 +1886,7 @@ static zend_string *php_pcre_replace_func_impl(pcre_cache_entry *pce, zend_strin
options, match_data, mctx);
while (1) {
- piece = subject + start_offset;
+ piece = subject + last_end_offset;
if (count >= 0 && limit) {
/* Check for too many substrings condition. */
@@ -1913,7 +1914,7 @@ matched:
/* Set the match location in subject */
match = subject + offsets[0];
- new_len = result_len + offsets[0] - start_offset; /* part before the match */
+ new_len = result_len + offsets[0] - last_end_offset; /* part before the match */
/* Use custom function to get replacement string and its length. */
eval_result = preg_do_repl_func(
@@ -1945,7 +1946,7 @@ matched:
limit--;
/* Advance to the next piece. */
- start_offset = offsets[1];
+ start_offset = last_end_offset = offsets[1];
/* If we have matched an empty string, mimic what Perl's /g options does.
This turns out to be rather cunning. First we set PCRE2_NOTEMPTY_ATSTART and try
@@ -1965,10 +1966,7 @@ matched:
to achieve this, unless we're already at the end of the string. */
if (start_offset < subject_len) {
size_t unit_len = calculate_unit_length(pce, piece);
-
start_offset += unit_len;
- memcpy(ZSTR_VAL(result) + result_len, piece, unit_len);
- result_len += unit_len;
} else {
goto not_matched;
}
@@ -1983,7 +1981,7 @@ not_matched:
result = zend_string_copy(subject_str);
break;
}
- new_len = result_len + subject_len - start_offset;
+ new_len = result_len + subject_len - last_end_offset;
if (new_len >= alloc_len) {
alloc_len = new_len; /* now we know exactly how long it is */
if (NULL != result) {
@@ -1993,8 +1991,8 @@ not_matched:
}
}
/* stick that last bit of string on our output */
- memcpy(ZSTR_VAL(result) + result_len, piece, subject_len - start_offset);
- result_len += subject_len - start_offset;
+ memcpy(ZSTR_VAL(result) + result_len, piece, subject_len - last_end_offset);
+ result_len += subject_len - last_end_offset;
ZSTR_VAL(result)[result_len] = '\0';
ZSTR_LEN(result) = result_len;
break;
diff --git a/ext/pcre/tests/bug79188.phpt b/ext/pcre/tests/bug79188.phpt
new file mode 100644
index 0000000000..875aad7b98
--- /dev/null
+++ b/ext/pcre/tests/bug79188.phpt
@@ -0,0 +1,13 @@
+--TEST--
+Bug #79188: Memory corruption in preg_replace/preg_replace_callback and unicode
+--FILE--
+<?php
+
+var_dump(preg_replace("//u", "", "a" . str_repeat("\u{1f612}", 10)));
+var_dump(preg_replace_callback(
+ "//u", function() { return ""; }, "a" . str_repeat("\u{1f612}", 10)));
+
+?>
+--EXPECT--
+string(41) "a😒😒😒😒😒😒😒😒😒😒"
+string(41) "a😒😒😒😒😒😒😒😒😒😒"