Commit cc22056f9db for php.net
commit cc22056f9db571859b2662b2f58cb69d72638773
Author: lazerg <lazerg2@gmail.com>
Date: Fri Aug 7 23:01:47 2026 +0500
Fix GH-23106: mb_strpos() overreads truncated UTF-8 haystacks (#23107)
offset_to_pointer_utf8() walks the haystack using the UTF-8 mblen table.
For a truncated multibyte sequence, the lead byte's table entry can exceed
the remaining bytes and leave the search start pointer past the end.
Passing that pointer to zend_memnstr() triggers an assertion in debug
builds and an out-of-bounds read in release builds, producing bogus
offsets or a crash for sufficiently long haystacks.
Clamp the pointer to the end of the string, as mb_str_split() already does
for the same table walk. Add regression coverage for forward and reverse
searches, negative offsets, and offsets beyond the haystack.
Fixes GH-23106.
Closes #23107
diff --git a/NEWS b/NEWS
index 33e9859d219..c367688672c 100644
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,10 @@ PHP NEWS
registrations are freed while still reachable from the cycle collector.
(Ilia Alshanetsky)
+- MBString:
+ . Fixed bug GH-23106 (mb_strpos() reads past the end of a haystack ending in
+ a truncated UTF-8 sequence). (Lazizbek Ergashev)
+
- Zip:
. Fixed ZipArchive::extractTo() ignoring files given in a non-list array.
(David Carlier)
diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c
index 4893390a826..6fbeb43fc1b 100644
--- a/ext/mbstring/mbstring.c
+++ b/ext/mbstring/mbstring.c
@@ -1891,6 +1891,9 @@ static unsigned char* offset_to_pointer_utf8(unsigned char *str, unsigned char *
}
pos += u8_tbl[*pos];
}
+ if (pos > end) {
+ pos = end;
+ }
return pos;
}
}
diff --git a/ext/mbstring/tests/gh23106.phpt b/ext/mbstring/tests/gh23106.phpt
new file mode 100644
index 00000000000..f55df0f2f08
--- /dev/null
+++ b/ext/mbstring/tests/gh23106.phpt
@@ -0,0 +1,22 @@
+--TEST--
+GH-23106 (mb_strpos() reads past the end of a haystack ending in a truncated UTF-8 sequence)
+--EXTENSIONS--
+mbstring
+--FILE--
+<?php
+var_dump(mb_strpos("AA\xf0\x90", "xyz", 3));
+var_dump(mb_strpos("AA\xf0\x90", "x", 3));
+var_dump(mb_strrpos("AA\xf0\x90", "A", 3));
+var_dump(mb_strrpos("AA\xf0\x90", "A", -1));
+try {
+ mb_strpos("AA\xf0\x90", "xyz", 4);
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+bool(false)
+bool(false)
+bool(false)
+int(1)
+mb_strpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack)