High patch-now ✔︎ verified CVSS 8.8 chrome OOB 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds write in Fonts
DescriptionOut of bounds write in Fonts
ComponentFonts
Bug ClassOOB
Action tierpatch-now — corruption primitive, web-reachable in default config (confirmed) — factors set by independent review by AI
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Independent review by AIConfirmed
Reviewer notes: none. Lines 501-507/506 are not in this excerpt (disclosed); they match 708bf4a0c80b^:501-507 in the CVE-2026-4679 material, unchanged by #5823. Attack step 1 attributes the 256-glyph cap to the reporter, who never mentions it (marked inferred).
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker503425922
Fix commit488922519f21 (harfbuzz (upstream, via Chromium mirror)) +3/-0
CWECWE-787 (Out-of-bounds write)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedMatej Smycka
Reported2026-04-16 · bounty $10000 (per vendor advisory)
Fix landed2026-04-15 (public git)
Disclosed2026-05-12
Patch gap27 days — fix public in source before the advisory

Background

`stch` stretching
An Arabic shaping feature where a glyph is replaced by fixed and repeating tiles, and apply_stch repeats the STCH_REPEATING tiles to fill the width of the surrounding word.
MEASURE / CUT passes
apply_stch loops for (step = MEASURE; step <= CUT; ...): MEASURE adds up extra_glyphs_needed per stretch run, and CUT copies glyphs backwards from j = 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_copies to max_copies, so one run produces at most STCH_MAX_GLYPHS = 256 glyphs (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

Re-analyzed on 2026-10-03 against the actual fix commit 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.

Key insight
The overflow handler from #5808 detected the wrap but left the wrapped total in place and only exited the inner loop, so the second pass still trusted the bad total. Setting the total to UINT_MAX forces the existing buffer-size check to fail, which turns the detected overflow into an abort before any writes happen.

Attack Path

  1. Serve a crafted web font A page loads a font whose stch lookup produces fixed and repeating tiles, with advances that push every run up to the 256-glyph cap (per the reporter; inferred).
  2. 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 the blink::HarfBuzzShaper → hb_shape → apply_stch path).
  3. Wrap the MEASURE total Adding up the capped per-run counts wraps extra_glyphs_needed, and the break at :608 leaves a small wrapped value behind.
  4. Out-of-bounds write in CUT ensure succeeds for the small size (:655), and CUT writes past the buffer through the wrapped j (:636-638).

Impact Assessment

A malicious web page can cause a heap out-of-bounds write of glyph info/position records in the renderer process, which the reporter describes as memory corruption in a sandboxed process that crashes on 64-bit builds because the wrapped index lands far outside the buffer. Whether the write can be controlled, for example on 32-bit builds, is not shown (inferred). The vendor rates it high severity, and the advisory does not report in-the-wild exploitation.

Files Changed

  • src/hb-ot-shaper-arabic.cc

Audit Directions

  • Error signalled by `break` in nested loops
    For 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 failure
    Helpers like hb_unsigned_add_overflows store the wrapped result even when they return true. Callers must discard or poison that output, not keep using it.
  • Sibling two-pass shapers
    Look 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.
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
Loading diff…

Original Bug Report

reported by [email protected]

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

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-face loads the font, lays out 16,909,322 × BEH ALEF pairs 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]

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

View on issue tracker