From f361c0aded1a9f3a52c2e5c4fb721da14bc51e7f Mon Sep 17 00:00:00 2001 From: Dhrupo Date: Mon, 2 Nov 2026 11:52:16 +0600 Subject: [PATCH] Fix GH-25024: clamp mb_ereg_replace() \k overrun on PHP-8.2/8.3 Backport of 509e27bae1cd (Closes GH-23499; fix by Ilia Alshanetsky). When a \k backref in the replacement lacks its closing delimiter, two error paths in mb_regex_substitute() advance p past eos and the following smart_str_appendl() copies the overshoot (a NUL terminator, or heap bytes past the string) into the result. Clamp both sites to eos, identical to PHP-8.4+. The fix shipped in 8.4.26 but was never backported to the still-supported 8.2 and 8.3 branches. --- NEWS | 4 ++++ ext/mbstring/php_mbregex.c | 5 +++-- ...mb_ereg_replace_kname_unterminated_nul.phpt | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 ext/mbstring/tests/mb_ereg_replace_kname_unterminated_nul.phpt diff --git a/NEWS b/NEWS index ad524d0bb94c..bb6b59d37d4b 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.2.36 +- MBString: + . Fixed mb_ereg_replace() emitting a NUL or out-of-bounds bytes in the + replacement when a \k backref has no closing delimiter. + (Ilia Alshanetsky) 21 Oct 2026, PHP 8.2.35 diff --git a/ext/mbstring/php_mbregex.c b/ext/mbstring/php_mbregex.c index 82479ecdfab4..0c4f667e0726 100644 --- a/ext/mbstring/php_mbregex.c +++ b/ext/mbstring/php_mbregex.c @@ -771,7 +771,7 @@ static inline void mb_regex_substitute( clen = (int) php_mb_mbchar_bytes(++p, enc); if (clen != 1 || p == eos || (p[0] != '<' && p[0] != '\'')) { /* not a backref delimiter */ - p += clen; + p = MIN(p + clen, eos); smart_str_appendl(pbuf, sp, p - sp); continue; } @@ -791,12 +791,13 @@ static inline void mb_regex_substitute( if (maybe_num && !isdigit((unsigned char)name_end[0])) maybe_num = 0; name_end++; } - p = name_end + 1; if (name_end - name < 1 || name_end >= eos) { /* the backref was empty or we failed to find the end delimiter */ + p = MIN(name_end + 1, eos); smart_str_appendl(pbuf, sp, p - sp); continue; } + p = name_end + 1; /* we have either a name or a number */ if (maybe_num) { if (!onig_noname_group_capture_is_active(regexp)) { diff --git a/ext/mbstring/tests/mb_ereg_replace_kname_unterminated_nul.phpt b/ext/mbstring/tests/mb_ereg_replace_kname_unterminated_nul.phpt new file mode 100644 index 000000000000..67a39dc9f95c --- /dev/null +++ b/ext/mbstring/tests/mb_ereg_replace_kname_unterminated_nul.phpt @@ -0,0 +1,18 @@ +--TEST-- +mb_ereg_replace() with unterminated \k backref must not embed a NUL byte +--EXTENSIONS-- +mbstring +--FILE-- + +--EXPECT-- +string(12) "5c6b3c6e756d" +string(12) "5c6b276e756d" +string(10) "615c6b3c6e" +string(8) "615c6bf0" +string(10) "5c5c6be282"