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)