CVE-2026-8558
Overview
Background
- `stch` stretching
- An Arabic shaping feature where a glyph is replaced by fixed and repeating tiles, and
apply_stchrepeats theSTCH_REPEATINGtiles to fill the width of the surrounding word. - MEASURE / CUT passes
apply_stchloopsfor (step = MEASURE; step <= CUT; ...): MEASURE adds upextra_glyphs_neededper stretch run, and CUT copies glyphs backwards fromj = count + extra_glyphs_needed(lines 501-507; this excerpt does not show them, but they are identical in the CVE-2026-4679 material).- Per-run cap
- The earlier follow-up #5823 clamps
n_copiestomax_copies, so one run produces at mostSTCH_MAX_GLYPHS = 256glyphs (src/hb-ot-shaper-arabic.cc:564,:594-601). - `hb_unsigned_add_overflows`
- The checked-add helper from #5808 (CVE-2026-4679), which returns true on wraparound but still stores the wrapped sum in its result pointer.
Root Cause Analysis
488922519f21. An earlier analysis was withdrawn because it had been written against a dependency roll (71a1e763a70a).This is a leftover gap in the CVE-2026-4679 fix (#5808 plus follow-up #5823): in MEASURE, each run’s n_copies * n_repeating goes through hb_unsigned_mul_overflows / hb_unsigned_add_overflows, and the add writes its result straight back into extra_glyphs_needed (src/hb-ot-shaper-arabic.cc:605-607). On overflow, the break at :608 exits only the inner per-run loop, not the enclosing step loop, so extra_glyphs_needed keeps the wrapped sum and any runs not yet measured are never counted. The count + extra_glyphs_needed check at :653-655 therefore passes, buffer->ensure grows the buffer only to the small wrapped size, and the step loop moves on to CUT. CUT has no early exit (:611-648): it walks every run and writes repeat = 1 + n_copies copies per repeating tile (:619-621) from a write head that starts at new_len = count + extra_glyphs_needed (line 506), so the unsigned j wraps below zero (:636) and info[j] / pos[j] are written out of bounds (:637-638) before the assert (j == 0) at :660 can catch it.
The fix sets extra_glyphs_needed = UINT_MAX before the break, and because count is nonzero whenever the per-run loop has run, the existing hb_unsigned_add_overflows (count, extra_glyphs_needed, &total_glyphs) at :654 now reports overflow, the break at :656 exits the step loop, and CUT never runs. The code differs slightly from the reporter’s description of a buffer sized by “the partial pre-overflow value”: with #5823’s cap, n_copies * n_repeating cannot overflow (:597-601), so the add at :607 is what fires, and it stores the wrapped sum, not the pre-overflow partial sum. Either way the allocation is smaller than what CUT writes.
UINT_MAX forces the existing buffer-size check to fail, which turns the detected overflow into an abort before any writes happen.Attack Path
- Serve a crafted web font
A page loads a font whose
stchlookup produces fixed and repeating tiles, with advances that push every run up to the 256-glyph cap (per the reporter; inferred). - Shape one very long Arabic run
Text with millions of stretch groups in a single run reaches
hb_shape, which the reporter attributes to Blink not splitting long script runs (inferred; the CVE-2026-4679 report’s stack shows theblink::HarfBuzzShaper→hb_shape→apply_stchpath). - Wrap the MEASURE total
Adding up the capped per-run counts wraps
extra_glyphs_needed, and thebreakat:608leaves a small wrapped value behind. - Out-of-bounds write in CUT
ensuresucceeds for the small size (:655), and CUT writes past the buffer through the wrappedj(:636-638).
Impact Assessment
Files Changed
src/hb-ot-shaper-arabic.cc
Audit Directions
- Error signalled by `break` in nested loopsFor every overflow or failure
break, check which loop it leaves and whether later code still uses the partially updated value. Prefer an explicit error flag or an early return. - Checked helpers that write on failureHelpers like
hb_unsigned_add_overflowsstore the wrapped result even when they return true. Callers must discard or poison that output, not keep using it. - Sibling two-pass shapersLook at other HarfBuzz and Blink code that sizes buffers in one pass and fills them in another, and check that the write pass can stop on its own if the size from the first pass is wrong.
Patch
diff --git a/src/hb-ot-shaper-arabic.cc b/src/hb-ot-shaper-arabic.cc
index 2a05af1..d33b909 100644
--- a/src/hb-ot-shaper-arabic.cc
+++ b/src/hb-ot-shaper-arabic.cc
@@ -605,7 +605,10 @@
unsigned int added_glyphs = 0;
if (unlikely (hb_unsigned_mul_overflows (n_copies, n_repeating, &added_glyphs) ||
hb_unsigned_add_overflows (extra_glyphs_needed, added_glyphs, &extra_glyphs_needed)))
+ {
+ extra_glyphs_needed = UINT_MAX;
break;
+ }
DEBUG_MSG (ARABIC, nullptr, "will add extra %u copies of repeating tiles", n_copies);
}
else
Original Bug Report
apply_stch uint32 accumulation OOB write (Chromium renderer SEGV)
Report description
apply_stch uint32 accumulation OOB write (Chromium renderer SEGV)
Bug location
Where do you want to report your vulnerability?
Chrome VRP – Report security issues affecting the Chrome browser. See program rules
Which URL (or repository) have you found the vulnerability in?
https://chromium.googlesource.com/chromium/src/third_party/+/refs/heads/main
The problem
Please describe the technical details of the vulnerability
- Upstream advisory: https://github.com/harfbuzz/harfbuzz/security/advisories/GHSA-5xrh-8c3h-7c49
- Upstream fix PR: https://github.com/harfbuzz/harfbuzz/pull/5929
Summary
In HarfBuzz apply_stch (src/hb-ot-shaper-arabic.cc), the MEASURE pass
accumulates extra_glyphs_needed (uint32) across multiple STCH groups in
one buffer. The overflow-detected break exits only the inner per-group
loop; the outer step loop continues to CUT, which walks all groups and
writes the full (non-wrapped) number of entries into a buffer sized by the
partial pre-overflow value. Result: heap OOB write.
Chromium is reachable because Blink feeds an entire Arabic script run to
hb_shape() with no fragment cap (Firefox has MAX_SHAPING_LENGTH=32760).
Repro
Artifacts (this directory):
stch_of3.ttf– 844 B crafted font;stch→beh → [tile_f, tile_r]poc.html–@font-faceloads the font, lays out 16,909,322 ×BEH ALEFpairs in one RTL run.
Serve and point an ASan Chromium at the page:
python3 -m http.server 8899 &
ASAN_OPTIONS="log_path=/tmp/asan.log:handle_segv=1:allow_user_segv_handler=0:detect_leaks=0:symbolize=1" \
~/asan/asan-chromium/content_shell --no-sandbox --disable-hang-monitor \
http://127.0.0.1:8899/poc.html
# takes 10-60s
Renderer SEGVs after page load; ASan log at /tmp/asan.log.<pid>.
Root cause
src/hb-ot-shaper-arabic.cc MEASURE block (pre-fix):
if (unlikely (hb_unsigned_mul_overflows (n_copies, n_repeating, &added_glyphs) ||
hb_unsigned_add_overflows (extra_glyphs_needed, added_glyphs,
&extra_glyphs_needed)))
break; // exits inner loop only
After the break, the outer if (step == MEASURE) { buffer->ensure (…); }
uses the partial extra_glyphs_needed → small allocation → CUT OOB.
Credit
Matej Smycka
Impact analysis
Any website the victim visits can trigger this bug by serving a crafted font and some Arabic text. It reliably crashes the renderer tab on 64bit as the write lands far outside the process memory. 32bit would land into the process. The 64bit is unlikely to be exploited as controlled write, however it may be possible.
The cause
What version of Chrome have you found the security issue in?
148.0.7750.0 [ASan content_shell, Linux x86_64]
Is the security issue related to a crash?
No, it is not related to a crash.
Choose the type of vulnerability
Memory Corruption (in a sandboxed process)
How would you like to be publicly acknowledged for your report?
Matej Smycka
- http://127.0.0.1:8899/poc.html
- https://bughunters.google.com/about/rules/5745167867576320/chrome-vulnerability-reward-program-rules
- https://chromium.googlesource.com/chromium/src/third_party/+/refs/heads/main
- https://github.com/harfbuzz/harfbuzz/pull/5929
- https://github.com/harfbuzz/harfbuzz/security/advisories/GHSA-5xrh-8c3h-7c49