Repository navigation
mb_strpos()/mb_strrpos(): the out-of-bounds read fixed in 8.4.27 (GH-23106) is still present in PHP-8.3 and PHP-8.2 #25025
Description
Description
This is not a new bug and not my finding. It is GH-23106, fixed in 8.4.27 /
8.5.12 and credited to Lazizbek Ergashev:
Fixed bug GH-23106 (
mb_strpos()reads past the end of a haystack ending in a
truncated UTF-8 sequence). (Lazizbek Ergashev)
Since it is already accepted upstream, there is nothing to re-argue about
whether it is a bug. The only question is the branches: PHP-8.3 and PHP-8.2
did not get the fix, and both are still in security support (8.3 to 2027-12-31,
8.2 to 2026-12-31).
Still present on both branches
The forward branch of the walk can step past end:
} else {
const unsigned char *u8_tbl = mbfl_encoding_utf8.mblen_table;
unsigned char *pos = str;
while (offset-- > 0) {
if (pos >= end) {
return NULL;
}
pos += u8_tbl[*pos]; /* the last step can overshoot end */
}
return pos;
}PHP-8.4 head adds the clamp before the return:
if (pos > end) {
pos = end;
}
return pos;PHP-8.3 and PHP-8.2 head do not have it.
This is easy to miss when grepping
The function has a different name and lives in a different file depending on the
branch, which is probably why the backport was not obvious:
| Branch | Name | File | Clamp |
|---|---|---|---|
PHP-8.4 / PHP-8.5 / master |
offset_to_pointer_utf8 |
ext/mbstring/mbstring.c:1900 |
yes |
PHP-8.3 |
offset_to_pointer_utf8 |
ext/mbstring/mbstring.c:1890 |
no |
PHP-8.2 |
mbfl_find_offset_utf8 |
ext/mbstring/libmbfl/mbfl/mbfilter.c:544 |
no |
(Line numbers are branch head as of 2026-10-29.)
So the 8.4 patch does not apply to 8.2 as-is. There the function is still
mbfl_find_offset_utf8() in libmbfl and its pointers are const unsigned char *,
so the clamp has to be rewritten against the older name and location. 8.3 is a
straight apply.
The following code
<?php
// haystack = "a\xF0" (2 chars, ends in a truncated 4-byte UTF-8 lead)
// needle = "\xE3" -- does NOT occur in the haystack
// offset = -1 -- takes the reverse && offset<0 path
$h = "a\xF0";
$n = "\xE3";
echo "haystack=", bin2hex($h), " len=", strlen($h), " mb_strlen=", mb_strlen($h,'UTF-8'), "\n";
echo "needle =", bin2hex($n), "\n";
$r = mb_strrpos($h, $n, -1, 'UTF-8');
echo "mb_strrpos => ", var_export($r, true), "\n";Resulted in this output / But I expected this output instead
There is no visible difference, and that is worth saying plainly — the
return value is the same on an affected and on a fixed build. The defect is the
read itself, so it shows up under instrumentation or ASAN rather than in the
output. I am filling this section in honestly rather than inventing a diff.
What I measured, with one probe reporting pos - end on every call:
| Build | probe hits | delta == 0 |
delta > 0 (out of bounds) |
|---|---|---|---|
| 8.3.36 | 1,773 | 54 | 507 (+1: 162, +2: 108, +3: 108, …) |
| 8.5.11 | 1,773 | 54 | 507 (identical) |
| 8.5.12 | 1,773 | 561 | 0 |
The cases that went out of bounds on 8.3.36 stop exactly at end on 8.5.12,
which is the clamp doing its job. 8.5.11 — the last release before the fix —
measures identical to 8.3.36, which is the cleanest demonstration that 8.3 is in
the pre-fix state.
Trigger conditions: a haystack ending in a truncated multi-byte lead byte
(0xC2–0xF4) and a negative offset. A sweep using only non-negative
offsets produced zero overshoots across 228 calls.
Which versions
7.4 is genuinely not affected: the walk is inlined there and followed by
p += needle_u8_len; if (p > e) goto out;, which neutralises the overshoot. The
probe was confirmed reached (96 and 132 times at the two sites) with zero
overshoots, so that is a measured negative rather than missing coverage. The
defect was introduced by the 8.0 refactor that extracted the walk into
mbfl_find_offset_utf8(). 8.1.31 and 8.2.35 both reproduce (8.1.31 measured
returning end + 3).
The ask
Apply the GH-23106 clamp to PHP-8.3, and to PHP-8.2 while that branch is
still in security support. The upstream fix is PR #23107; it applies to 8.3 as
is, and for 8.2 it has to be rewritten for mbfl_find_offset_utf8() in libmbfl,
per the table above.
I am not asking for a CVE or for credit; the credit belongs to Lazizbek
Ergashev. If you judge 8.3 fine as it is, or 8.2 too close to end of life to
warrant a release, that is a reasonable call and I will not argue it.
PHP Version
PHP 8.3.36 (cli) (built from source, gcc 13.3, NTS)
Operating System
Ubuntu 24.04 (WSL2)
Activity
youkidearitai commentedon Nov 1, 2026
8.3 and 8.2 are security fixes only. So probably we can't fixes these bugs
aygydiewr221 commentedon Nov 1, 2026
Understood — thanks for looking.
The only reason I raised it was whether an out-of-bounds read counts as a
security fix rather than a functional one. If it doesn't, that's a complete
answer and I am happy to leave both branches as they are.
youkidearitai commentedon Nov 1, 2026
@aygydiewr221 If this issue is security report, Please report via https://github.com/php/php-src/security/advisories/new .
VishnuSrivatsava commentedon Nov 1, 2026
Understood, thank you for the quick response. Closing both PRs. The fix is already available on master since 8.4.27, so the regression test there covers it. If the security classification for the stable branches ever changes, the backports are easy to revive from the branches on my fork.