diff options
-rw-r--r-- | NEWS | 7 | ||||
-rw-r--r-- | ext/pcre/php_pcre.c | 34 | ||||
-rw-r--r-- | ext/pcre/tests/bug79188.phpt | 13 |
3 files changed, 36 insertions, 18 deletions
@@ -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😒😒😒😒😒😒😒😒😒😒" |