Skip to content

Fix GH-23106 / GH-25025: Clamp pos in offset_to_pointer_utf8 to prevent out-of-bounds read on truncated UTF-8 - #25167

Closed
AdamMagued wants to merge 1 commit into
php:PHP-8.3from
AdamMagued:fix-issue-25025-php83-mbstring-clamp
Closed

AdamMagued wants to merge 1 commit into
php:PHP-8.3from
AdamMagued:fix-issue-25025-php83-mbstring-clamp

Conversation

@AdamMagued

Copy link
Copy Markdown

Fixes GH-25025 and GH-23106 for the PHP-8.3 branch.

Backport of master/8.4 commit cc22056 (PR #23107, credited to Lazizbek Ergashev), which resolved GH-23106 in 8.4.27 and 8.5.12.

Root Cause

In ext/mbstring/mbstring.c, offset_to_pointer_utf8() walks the haystack forward using mbfl_encoding_utf8.mblen_table:

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];
}
return pos;

When str ends in a truncated multi-byte sequence (such as \xf0\x90), u8_tbl[*pos] advances pos by the full expected sequence length (4 bytes) rather than the remaining bytes in the buffer. The function then returns a pointer beyond end.

When callers like zend_memnstr() or zend_memnrstr() receive this pointer:

  1. Debug builds fail the assert(end >= p) invariant check in zend_operators.h.
  2. Release builds perform an out-of-bounds read (CWE-125) past the haystack buffer, returning non-deterministic offsets or segfaulting on long inputs.

Fix

Add the clamp if (pos > end) { pos = end; } identical to master/8.4 before returning pos.

Regression Test

Ported and extended regression test in ext/mbstring/tests/gh23106.phpt verifying truncated UTF-8 haystacks with positive and negative offsets under mb_strpos() and mb_strrpos().

Verification

  • Pre-fix: gh23106.phpt reproduces the out-of-bounds read (mb_strpos("AA\xf0\x90", "x", 3) returns int(29) instead of bool(false)).
  • Post-fix: gh23106.phpt passes cleanly.
  • Full ext/mbstring test suite in container sandbox: 416 passed, 0 failed, 9 skipped.

… haystack

Backport upstream fix cc22056 (phpGH-23106 / PR php#23107 by Lazizbek Ergashev)
to PHP-8.3.

When offset_to_pointer_utf8() performs a forward walk over a haystack
ending in a truncated multi-byte sequence, the lead byte length exceeds
the remaining buffer length, causing the returned pointer to overshoot
the end pointer. This violates the end >= p invariant expected by callers
such as zend_memnstr() and zend_memnrstr(), leading to assertion failures
in debug builds and out-of-bounds reads in release builds.

Clamp pos to end after the walk identical to master and PHP-8.4. Add
regression test covering forward and negative offsets with truncated
haystacks.

Fixes phpGH-25025.
Fixes phpGH-23106.
@LamentXU123

LamentXU123 commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Hi, 8.3 is a security-only branch and will not accept any general bug fixes. If you can prove to 8.3 RMs that this qualifies as a security issue, you can reopen this. Also, Yuya has mentioned this in the github issue.
Thanks anyways.

@LamentXU123 LamentXU123 closed this Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants