Closed Bug 2021863 (CVE-2026-4699) Opened 8 months ago Closed 7 months ago

Heap buffer overflow (OOB write) in HarfBuzz apply_stch() via crafted web font

Categories

(Core :: Layout: Text and Fonts, defect)

defect

Tracking

()

VERIFIED FIXED
150 Branch
Tracking Status
firefox-esr115 149+ verified
firefox-esr140 149+ verified
firefox148 --- wontfix
firefox149 + verified
firefox150 + verified

People

(Reporter: matejsmycka, Assigned: jfkthame)

References

Details

(Keywords: csectype-bounds, reporter-external, sec-high, Whiteboard: [upstream bug is public][client-bounty-form][adv-main149+][adv-ESR140.9+][adv-ESR115.34+])

Attachments

(1 file, 4 external files)

90.11 KB, application/zip
Details
Attached file poc.zip —

An integer overflow in HarfBuzz's apply_stch() function causes a heap out-of-bounds write that crashes Firefox's content process. A malicious webpage can trigger this by serving a crafted OpenType font (which passes OTS) and rendering Arabic text via canvas or DOM. No user interaction is required beyond navigating to the page.

This bug is in upstream HarfBuzz and will be reported simultaneously to the HarfBuzz maintainers.

Affected Product

  • Firefox: 148.0 (Manjaro Linux, dynamically linked against system HarfBuzz 12.3.2)
  • HarfBuzz: All versions up to and including 13.0.0 (current upstream)
  • OS: Linux x86_64 (confirmed), all platforms affected (platform-independent code)

Steps to Reproduce

  1. Open the attached poc_firefox.html in Firefox (self-contained, crafted font embedded as base64 data URI)
  2. The content process crashes with SIGSEGV

The page loads a crafted font via @font-face (CSS data URI), builds a string of 131,082 Arabic characters (3 groups of [1 baa U+0628 + 43,693 alefs U+0627]), and uses canvas.measureText() or DOM rendering to trigger HarfBuzz text shaping.

Expected Behavior

The page renders without crashing.

Actual Behavior

The Firefox content process crashes with SIGSEGV (signal 11) when HarfBuzz's apply_stch() function writes glyph data past the end of the heap-allocated buffer.

Firefox Crash Report

ProcessType: content
CrashType: SIGSEGV / SEGV_MAPERR
ContentSandboxLevel: 6
DOMFissionEnabled: 1
Version: 148.0

ASAN Stack Trace

Built HarfBuzz 13.0.0 from upstream source with -Db_sanitize=address, reproduced with a standalone C test program (./test_multi_group StchFx.ttf 1000 43693 3):

==417213==ERROR: AddressSanitizer: SEGV on unknown address 0x7b9fcd0477ec
==417213==The signal is caused by a WRITE memory access.
    #0 apply_stch ../src/hb-ot-shaper-arabic.cc:616
    #1 postprocess_glyphs_arabic ../src/hb-ot-shaper-arabic.cc:652
    #2 hb_ot_substitute_post ../src/hb-ot-shape.cc:963
    #3 hb_ot_shape_internal ../src/hb-ot-shape.cc:1195
    #4 _hb_ot_shape ../src/hb-ot-shape.cc:1213
    #5 _hb_shape_plan_execute_internal ../src/hb-shaper-list.hh:47
    #6 hb_shape_plan_execute ../src/hb-shape-plan.cc:452
    #7 hb_shape_full ../src/hb-shape.cc:150
    #8 hb_shape ../src/hb-shape.cc:194
    #9 main test_multi_group.c
SUMMARY: AddressSanitizer: SEGV ../src/hb-ot-shaper-arabic.cc:616 in apply_stch

Line 616 is info[j] = info[k - 1]; — writing glyph info through a wrapped unsigned index j into unmapped memory.

Root Cause

File: src/hb-ot-shaper-arabic.cc, function apply_stch()

The function implements Arabic kashida stretching (STCH feature) in two passes: MEASURE and CUT.

During MEASURE, it accumulates extra glyphs needed across all STCH groups:

unsigned int extra_glyphs_needed = 0;  // 32-bit unsigned

// ... for each STCH group:
extra_glyphs_needed += n_copies * n_repeating;  // line 587

The buffer is then allocated to hold count + extra_glyphs_needed glyphs. The CUT pass writes n_copies repetitions of each repeating tile into the buffer using a backwards-decrementing index j:

--j;
info[j] = info[k - 1];  // line 616 — the crashing write
pos[j] = pos[k - 1];    // line 617

The bug: extra_glyphs_needed accumulates across all STCH groups in the buffer. A crafted font that produces M groups, each contributing a large product P = n_copies * n_repeating, can make the sum M × P overflow uint32, wrapping to a small value. The buffer is then under-allocated, and the CUT pass writes past the end of the buffer.

Important detail: buffer ordering

The apply_stch() function runs during hb_ot_substitute_post(), which is called AFTER hb_ot_position(). For RTL text (Arabic), hb_ot_position() reverses the buffer into visual order (line 1102-1103 in hb-ot-shape.cc). The apply_stch() function does NOT reverse the buffer back for RTL (line 488: if (!rtl) buffer->reverse()). As a result, apply_stch processes the buffer in visual (reversed) order, where each STCH tile group has its context alefs BEFORE it. This means all M groups contribute to extra_glyphs_needed, not just M-1.

Firefox-Specific Details

Firefox's HarfBuzz integration sets hb_font_set_scale(font, upem, upem) where upem=1000, making the HarfBuzz scale independent of CSS font-size. This means:

  • alef_adv = 32767 (raw design units, confirmed via canvas.measureText: 524.27px at 16px = 32767 × 16/1000)
  • tile_adv = 1 (raw design units)
  • The overflow parameters are constant regardless of font-size

Overflow Arithmetic

The crafted font maps baa (U+0628) to 5000 tile glyphs via MultipleSubst under the stch feature. Tiles alternate STCH_FIXED and STCH_REPEATING (2500 each), all with advance=1 at upem=1000.

The stch feature is applied first in the Arabic shaper (before init/medi/fina/isol), so baa is expanded to tiles before joining features run. The joining features then map alef (U+0627) to alef.fina/alef.isol, all with advance=32767.

With Firefox's scale=1000 (upem), N=43,693 alefs per group, M=3 groups:

For each STCH group:

  • w_total = 43,693 × 32,767 = 1,431,688,531
  • w_fixed = 2,500 × 1 = 2,500
  • w_repeating = 2,500 × 1 = 2,500
  • w_remaining = 1,431,688,531 − 2,500 = 1,431,686,031
  • n_copies = 1,431,686,031 / 2,500 − 1 = 572,673
  • Shortfall = 1,431,686,031 − 2,500 × 572,674 = 1,031 > 0 → ++n_copies = 572,674
  • Per-group product: P = 572,674 × 2,500 = 1,431,685,000

Accumulation across 3 groups (extra_glyphs_needed is unsigned int, 32-bit):

  • After group 1: 1,431,685,000
  • After group 2: 2,863,370,000
  • After group 3: 4,295,055,000 → wraps uint32 to 87,704 (4,295,055,000 − 4,294,967,296 = 87,704)

After GSUB, buffer count = 131,082 + 3 × 4,999 = 146,079:

  • Buffer allocated for: 146,079 + 87,704 = 233,783 glyphs (~9.4 MB)
  • CUT pass attempts to write: 146,079 + 3 × 1,431,685,000 = 4,295,201,079 entries → SIGSEGV

Font Requirements for Firefox

Firefox requires Arabic shaping features (init/medi/fina/isol) to use a web font for Arabic text. Without them, Firefox falls back to a system Arabic font, preventing the stch feature from triggering. The crafted font includes these features (SingleSubst identity substitutions for alef and baa variants) to satisfy Firefox's requirements.

What Was Demonstrated

  • Content process crash (SIGSEGV) triggered by web content — a crafted font served via @font-face with a data URI
  • The crafted font passes OTS (OpenType Sanitizer) — confirmed by ots-sanitize and successful font loading in Firefox
  • The crash is a WRITE to an unmapped address (confirmed by ASAN "caused by a WRITE memory access")
  • Reproduced on system HarfBuzz 12.3.2 (Arch/Manjaro Firefox 148.0) and upstream HarfBuzz 13.0.0 (ASAN build)
  • Firefox Fission/Content Sandbox is enabled (level 6), so this is a sandbox escape prerequisite — the content process crash is within the sandbox

Attachments

  1. poc_firefox.html — Self-contained PoC (crafted font embedded as base64 data URI). Open in Firefox to crash the content process.
  2. StchFx.ttf — The crafted OpenType font with Arabic shaping features (190KB).
  3. gen_firefox_font.py — Font generator script (requires fonttools).
  4. test_multi_group.c — Standalone C reproduction program for ASAN testing. Usage: ./test_multi_group StchFx.ttf 1000 43693 3 (scale=1000 matches Firefox's upem-based scaling).
Flags: sec-bounty?
Group: firefox-core-security → gfx-core-security
Component: Security → Graphics: Text
Keywords: csectype-bounds
Product: Firefox → Core

I think this is the upstream fix here: https://github.com/harfbuzz/harfbuzz/pull/5808

Jonathan, what's the process here? Do we just update harfbuzz or do we cherry-pick commits?

Severity: -- → S3
Flags: needinfo?(jfkthame)

Usually we just take new releases when they appear, but we have sometimes cherry-picked an extra commit if there's a fix we want to get in right away (and we know it'll be in future releases, so we don't have to worry about maintaining local patches across updates).

Given that this is an OOB write, which seems potentially quite bad, maybe it's worth doing the cherry-pick and uplifting it to beta/esr. (We wouldn't usually uplift an entire harfbuzz update, I think.)

I'm mostly OOO this week, but happy to rubber-stamp a cherry-pick patch if you want to prepare one; or I expect Lee would, too.

Flags: needinfo?(jfkthame)

Nope, https://github.com/harfbuzz/harfbuzz/pull/5808 did not fixed it. I will send link to fix after we merge my PR.

I haven't confirmed this, but I'll mark it sec-high assuming the report is valid.

Keywords: sec-high

The severity field for this bug is set to S3. However, the bug is flagged with the sec-high keyword.
:lsalzman, could you consider increasing the severity of this security bug?

For more information, please visit BugBot documentation.

Flags: needinfo?(lsalzman)
Component: Graphics: Text → Layout: Text and Fonts
Flags: needinfo?(lsalzman)
Group: gfx-core-security → layout-core-security

The upstream has fixed the vulnerability with this pull request: https://github.com/harfbuzz/harfbuzz/pull/5823.

There is a new upstream release (13.1.0, see bug 2022446) that includes this fix; we'll vendor this shortly. However, given the nature of the 'stch' issues (OOB-write), I think we could consider first cherry-picking https://github.com/harfbuzz/harfbuzz/pull/5808 and https://github.com/harfbuzz/harfbuzz/pull/5823 and uplifting these fixes, while the full 13.1.0 will ride the normal train.

Attached file (secure) —
Assignee: nobody → jfkthame
Status: UNCONFIRMED → ASSIGNED
Ever confirmed: true

Comment on attachment 9552162 [details]
(secure)

Security Approval Request

  • How easily could an exploit be constructed based on the patch?: It's pretty clear from the patch that it's fixing potential arithmetic overflows; given that, it's not hard to create a content-process-crashing font (especially with additional hints from the descriptions of the upstream PRs, which are public).

A more serious exploit (such as corrupting heap data to achieve arbitrary code execution) seems harder, but is probably possible for a knowledgeable attacker.

  • Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?: No
  • Which branches (beta, release, and/or ESR) are affected by this flaw, and do the release status flags reflect this affected/unaffected state correctly?: all; yes
  • If not all supported branches, which bug introduced the flaw?: None
  • Do you have backports for the affected branches?: Yes
  • If not, how different, hard to create, and risky will they be?: Checked locally that the patch transplants cleanly to beta and esr140; transplants with minor fuzz to esr115
  • How likely is this patch to cause regressions; how much testing does it need?: Minimal risk: cherry-picking released fixes from upstream
  • Is the patch ready to land after security approval is given?: Yes
  • Is Android affected?: Yes
Attachment #9552162 - Flags: sec-approval?

Comment on attachment 9552162 [details]
(secure)

sec-approval+ to land now and request uplifts

Attachment #9552162 - Flags: sec-approval? → sec-approval+

The public release notes mention this bug. It's not flagged with a CVE or advisory, but the language mentions "overflow" and "malicious fonts"
https://github.com/harfbuzz/harfbuzz/releases/tag/13.1.0#:~:text=Fix%20overflow%20when%20applying%20stch%20feature%20with%20malicious%20fonts

Is the Fx149 release soon enough that we don't have to call this sec-critical? Did the harfbuzz folks check in a testcase with a font that triggers this bug?

Flags: needinfo?(pascalc)
Flags: needinfo?(jfkthame)
Flags: needinfo?(fbraun)
Whiteboard: [client-bounty-form] → [upstream bug is public][client-bounty-form]

Happy to take into 149, we still have a couple of betas.

Flags: needinfo?(pascalc)

(In reply to Daniel Veditz [:dveditz] from comment #11)

The public release notes mention this bug. It's not flagged with a CVE or advisory, but the language mentions "overflow" and "malicious fonts"
https://github.com/harfbuzz/harfbuzz/releases/tag/13.1.0#:~:text=Fix%20overflow%20when%20applying%20stch%20feature%20with%20malicious%20fonts

Is the Fx149 release soon enough that we don't have to call this sec-critical?

IDK what the threshold there would be.... we haven't seen a PoC for an actual exploit related to this (as opposed to a content-process crash), and it seems like it might be difficult to come up with something predictable/reliable, given that it would depend on heap allocation patterns that may not be easy to predict/control. But we shouldn't underestimate the ingenuity of folks...

Did the harfbuzz folks check in a testcase with a font that triggers this bug?

I don't recall seeing a testcase checked in to upstream CI; the fix was verified with manual testing, AIUI.

Flags: needinfo?(jfkthame)
Pushed by jkew@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/4b5b000117cf https://hg.mozilla.org/integration/autoland/rev/792de7175b4d Cherry-pick 'stch' fixes #5808 and #5823 from harfbuzz upstream. r=gfx-reviewers,bradwerth

firefox-beta Uplift Approval Request

  • User impact if declined/Reason for urgency: Out-of-bounds write, leading to crash, but also potential for heap corruption, possibly arbitrary code execution?
  • Code covered by automated testing?: no
  • Fix verified in Nightly?: no
  • Needs manual QE testing?: yes
  • Steps to reproduce for manual QE testing: Simply load the poc_firefox.html testcase from the attached poc.zip archive; it should NOT crash the Firefox tab.
  • Risk associated with taking this patch: low
  • Explanation of risk level: Cherry-picking (already-released) fixes from upstream to avoid arithmetic overflow.
  • String changes made/needed?: None
  • Is Android affected?: yes
Attachment #9552581 - Flags: approval-mozilla-beta?
Flags: qe-verify+
Attached file (secure) —

firefox-esr115 Uplift Approval Request

  • User impact if declined/Reason for urgency: Out-of-bounds write, leading to crash, but also potential for heap corruption, possibly arbitrary code execution?
  • Code covered by automated testing?: no
  • Fix verified in Nightly?: no
  • Needs manual QE testing?: yes
  • Steps to reproduce for manual QE testing: Simply load the poc_firefox.html testcase from the attached poc.zip archive; it should NOT crash the Firefox tab.
  • Risk associated with taking this patch: low
  • Explanation of risk level: Cherry-picking (already-released) fixes from upstream to avoid arithmetic overflow.
  • String changes made/needed?: None
  • Is Android affected?: yes
Attachment #9552582 - Flags: approval-mozilla-esr115?
Attached file (secure) —

firefox-esr140 Uplift Approval Request

  • User impact if declined/Reason for urgency: Out-of-bounds write, leading to crash, but also potential for heap corruption, possibly arbitrary code execution?
  • Code covered by automated testing?: no
  • Fix verified in Nightly?: no
  • Needs manual QE testing?: yes
  • Steps to reproduce for manual QE testing: Simply load the poc_firefox.html testcase from the attached poc.zip archive; it should NOT crash the Firefox tab.
  • Risk associated with taking this patch: low
  • Explanation of risk level: Cherry-picking (already-released) fixes from upstream to avoid arithmetic overflow.
  • String changes made/needed?: None
  • Is Android affected?: yes
Attachment #9552583 - Flags: approval-mozilla-esr140?
Attached file (secure) —

The bug is marked as tracked for firefox149 (beta) and tracked for firefox150 (nightly). However, the bug still has low severity.

:fgriffith, could you please increase the severity for this tracked bug? If you disagree with the tracking decision, please talk with the release managers.

For more information, please visit BugBot documentation.

Flags: needinfo?(fgriffith)
Group: layout-core-security → core-security-release
Status: ASSIGNED → RESOLVED
Closed: 7 months ago
Resolution: --- → FIXED
Target Milestone: --- → 150 Branch
Attachment #9552581 - Flags: approval-mozilla-beta? → approval-mozilla-beta+
Attachment #9552582 - Flags: approval-mozilla-esr115? → approval-mozilla-esr115+
Attachment #9552583 - Flags: approval-mozilla-esr140? → approval-mozilla-esr140+
QA Whiteboard: [sec] [uplift] [qa-triage-done-c150/b149] [qa-ver-needed-c150/b149]

Reproduced the issue on Firefox 150.a01 (2026-03-09) by following the information provided in Comment 0.

The issue is fixed on Firefox ESR 115.34.0, Firefox ESR 140.9.0, Firefox 149.0b9 and Firefox 150.0a1 (2026-03-15). Tests were performed on Ubuntu 24.04, Windows 11 and macOS 26.3.1.

Status: RESOLVED → VERIFIED
QA Whiteboard: [sec] [uplift] [qa-triage-done-c150/b149] [qa-ver-needed-c150/b149] → [sec] [uplift] [qa-triage-done-c150/b149] [qa-ver-done-c150/b149]
Flags: qe-verify+
QA Contact: csasca

Issue closed. Clearing NI. Thanks JKew!

Flags: needinfo?(fgriffith)
Whiteboard: [upstream bug is public][client-bounty-form] → [upstream bug is public][client-bounty-form][adv-main149+][adv-ESR140.9+][adv-ESR115.34+]
Alias: CVE-2026-4699
Flags: sec-bounty? → sec-bounty+

(My understanding is that we have changed / are changing the policy about what we call critical. Someone releasing a security update for a third-party is not the same as a component being exploited in the wild)

Flags: needinfo?(fbraun)
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: