Skip to content

Commit 13bfa9f

Browse files
committed
Fixed bug #79188
1 parent e3632fd commit 13bfa9f

File tree

3 files changed

+33
-18
lines changed

3 files changed

+33
-18
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ PHP NEWS
22
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
33
?? ??? ????, PHP 7.3.16
44

5+
- PCRE:
6+
. Fixed bug #79188 (Memory corruption in preg_replace/preg_replace_callback
7+
and unicode). (Nikita)
8+
59
20 Feb 2020, PHP 7.3.15
610

711
- Core:

ext/pcre/php_pcre.c

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,6 +1574,7 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su
15741574
size_t match_len; /* Length of the current match */
15751575
int backref; /* Backreference number */
15761576
PCRE2_SIZE start_offset; /* Where the new search starts */
1577+
size_t last_end_offset; /* Where the last search ended */
15771578
char *walkbuf, /* Location of current replacement in the result */
15781579
*walk, /* Used to walk the replacement string */
15791580
*match, /* The current match */
@@ -1592,6 +1593,7 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su
15921593
/* Initialize */
15931594
match = NULL;
15941595
start_offset = 0;
1596+
last_end_offset = 0;
15951597
result_len = 0;
15961598
PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
15971599

@@ -1618,7 +1620,7 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su
16181620
options, match_data, mctx);
16191621

16201622
while (1) {
1621-
piece = subject + start_offset;
1623+
piece = subject + last_end_offset;
16221624

16231625
if (count >= 0 && limit > 0) {
16241626
zend_bool simple_string;
@@ -1648,7 +1650,7 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su
16481650
/* Set the match location in subject */
16491651
match = subject + offsets[0];
16501652

1651-
new_len = result_len + offsets[0] - start_offset; /* part before the match */
1653+
new_len = result_len + offsets[0] - last_end_offset; /* part before the match */
16521654

16531655
walk = ZSTR_VAL(replace_str);
16541656
replace_end = walk + ZSTR_LEN(replace_str);
@@ -1725,7 +1727,7 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su
17251727
limit--;
17261728

17271729
/* Advance to the next piece. */
1728-
start_offset = offsets[1];
1730+
start_offset = last_end_offset = offsets[1];
17291731

17301732
/* If we have matched an empty string, mimic what Perl's /g options does.
17311733
This turns out to be rather cunning. First we set PCRE2_NOTEMPTY_ATSTART and try
@@ -1745,10 +1747,7 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su
17451747
to achieve this, unless we're already at the end of the string. */
17461748
if (start_offset < subject_len) {
17471749
size_t unit_len = calculate_unit_length(pce, piece);
1748-
17491750
start_offset += unit_len;
1750-
memcpy(ZSTR_VAL(result) + result_len, piece, unit_len);
1751-
result_len += unit_len;
17521751
} else {
17531752
goto not_matched;
17541753
}
@@ -1763,7 +1762,7 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su
17631762
result = zend_string_copy(subject_str);
17641763
break;
17651764
}
1766-
new_len = result_len + subject_len - start_offset;
1765+
new_len = result_len + subject_len - last_end_offset;
17671766
if (new_len >= alloc_len) {
17681767
alloc_len = new_len; /* now we know exactly how long it is */
17691768
if (NULL != result) {
@@ -1773,8 +1772,8 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su
17731772
}
17741773
}
17751774
/* stick that last bit of string on our output */
1776-
memcpy(ZSTR_VAL(result) + result_len, piece, subject_len - start_offset);
1777-
result_len += subject_len - start_offset;
1775+
memcpy(ZSTR_VAL(result) + result_len, piece, subject_len - last_end_offset);
1776+
result_len += subject_len - last_end_offset;
17781777
ZSTR_VAL(result)[result_len] = '\0';
17791778
ZSTR_LEN(result) = result_len;
17801779
break;
@@ -1816,6 +1815,7 @@ static zend_string *php_pcre_replace_func_impl(pcre_cache_entry *pce, zend_strin
18161815
size_t new_len; /* Length of needed storage */
18171816
size_t alloc_len; /* Actual allocated length */
18181817
PCRE2_SIZE start_offset; /* Where the new search starts */
1818+
size_t last_end_offset; /* Where the last search ended */
18191819
char *match, /* The current match */
18201820
*piece; /* The current piece of subject */
18211821
size_t result_len; /* Length of result */
@@ -1845,6 +1845,7 @@ static zend_string *php_pcre_replace_func_impl(pcre_cache_entry *pce, zend_strin
18451845
/* Initialize */
18461846
match = NULL;
18471847
start_offset = 0;
1848+
last_end_offset = 0;
18481849
result_len = 0;
18491850
PCRE_G(error_code) = PHP_PCRE_NO_ERROR;
18501851

@@ -1877,7 +1878,7 @@ static zend_string *php_pcre_replace_func_impl(pcre_cache_entry *pce, zend_strin
18771878
options, match_data, mctx);
18781879

18791880
while (1) {
1880-
piece = subject + start_offset;
1881+
piece = subject + last_end_offset;
18811882

18821883
if (count >= 0 && limit) {
18831884
/* Check for too many substrings condition. */
@@ -1905,7 +1906,7 @@ static zend_string *php_pcre_replace_func_impl(pcre_cache_entry *pce, zend_strin
19051906
/* Set the match location in subject */
19061907
match = subject + offsets[0];
19071908

1908-
new_len = result_len + offsets[0] - start_offset; /* part before the match */
1909+
new_len = result_len + offsets[0] - last_end_offset; /* part before the match */
19091910

19101911
/* Use custom function to get replacement string and its length. */
19111912
eval_result = preg_do_repl_func(fci, fcc, subject, offsets, subpat_names, count,
@@ -1936,7 +1937,7 @@ static zend_string *php_pcre_replace_func_impl(pcre_cache_entry *pce, zend_strin
19361937
limit--;
19371938

19381939
/* Advance to the next piece. */
1939-
start_offset = offsets[1];
1940+
start_offset = last_end_offset = offsets[1];
19401941

19411942
/* If we have matched an empty string, mimic what Perl's /g options does.
19421943
This turns out to be rather cunning. First we set PCRE2_NOTEMPTY_ATSTART and try
@@ -1956,10 +1957,7 @@ static zend_string *php_pcre_replace_func_impl(pcre_cache_entry *pce, zend_strin
19561957
to achieve this, unless we're already at the end of the string. */
19571958
if (start_offset < subject_len) {
19581959
size_t unit_len = calculate_unit_length(pce, piece);
1959-
19601960
start_offset += unit_len;
1961-
memcpy(ZSTR_VAL(result) + result_len, piece, unit_len);
1962-
result_len += unit_len;
19631961
} else {
19641962
goto not_matched;
19651963
}
@@ -1974,7 +1972,7 @@ static zend_string *php_pcre_replace_func_impl(pcre_cache_entry *pce, zend_strin
19741972
result = zend_string_copy(subject_str);
19751973
break;
19761974
}
1977-
new_len = result_len + subject_len - start_offset;
1975+
new_len = result_len + subject_len - last_end_offset;
19781976
if (new_len >= alloc_len) {
19791977
alloc_len = new_len; /* now we know exactly how long it is */
19801978
if (NULL != result) {
@@ -1984,8 +1982,8 @@ static zend_string *php_pcre_replace_func_impl(pcre_cache_entry *pce, zend_strin
19841982
}
19851983
}
19861984
/* stick that last bit of string on our output */
1987-
memcpy(ZSTR_VAL(result) + result_len, piece, subject_len - start_offset);
1988-
result_len += subject_len - start_offset;
1985+
memcpy(ZSTR_VAL(result) + result_len, piece, subject_len - last_end_offset);
1986+
result_len += subject_len - last_end_offset;
19891987
ZSTR_VAL(result)[result_len] = '\0';
19901988
ZSTR_LEN(result) = result_len;
19911989
break;

ext/pcre/tests/bug79188.phpt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
Bug #79188: Memory corruption in preg_replace/preg_replace_callback and unicode
3+
--FILE--
4+
<?php
5+
6+
var_dump(preg_replace("//u", "", "a" . str_repeat("\u{1f612}", 10)));
7+
var_dump(preg_replace_callback(
8+
"//u", function() { return ""; }, "a" . str_repeat("\u{1f612}", 10)));
9+
10+
?>
11+
--EXPECT--
12+
string(41) "a😒😒😒😒😒😒😒😒😒😒"
13+
string(41) "a😒😒😒😒😒😒😒😒😒😒"

0 commit comments

Comments
 (0)