Merge branch 'PHP-7.3' into PHP-7.4

* PHP-7.3:
  Fixed bug #79188
This commit is contained in:
Nikita Popov 2020-02-05 11:19:55 +01:00
commit e30f52b919
3 changed files with 36 additions and 18 deletions

7
NEWS
View file

@ -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:

View file

@ -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;

View file

@ -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😒😒😒😒😒😒😒😒😒😒"