Uninitialized heap disclosure through a crafted web font
Firefox sanitizes web fonts through OTS before use.
Abdulaziz Alasaiqah
CVE-2026-74945 · RESOLVED FIXED
Vendor
Mozilla Firefox
Component
Core / Graphics: Text
Class
Uninitialized Memory Disclosure
CWE
908, 200
CVSS
6.5 Medium (Mozilla)
Fixed in
Firefox 154, ESR 115.39, ESR 140.14, ESR 153.1; Thunderbird 140.14, 153.1, 154
Interaction
None, page load only
Summary
Firefox sanitizes web fonts through OTS before use. OTS's expanding output stream allocates with a raw reallocator that never zeroes memory, and its Seek accepts any position up to the allocation size without zero-filling the gap. When a font's cmap carries a format-14 subtable with a large nonDefaultUVSOffset, OTS seeks forward and leaves an uninitialized-heap gap inside the emitted font. A companion format-4 subtable maps U+FFFF into that gap, so measuring the character with canvas measureText returns an advance derived from uninitialized heap, about two attacker-recoverable bytes per lookup over a roughly 64 KB window.
Root cause
Three independent defects line up. The OTS output allocator never zeroes memory. gfxOTSExpandingMemoryStream::Seek does not zero the skipped region, and forget() sizes the buffer to the highest offset reached, so any gap is handed to the caller carrying uninitialized heap. Finally the format-4 glyph lookup bounds-checks against the remainder of the whole cmap table rather than the subtable, so a jump into the neighbouring subtable's uninitialized bytes still passes the check.
Proof of concept
- Serve a WOFF or OTF via @font-face whose cmap pairs a format-4 subtable, with an odd id_range_offset on the last 0xFFFF segment, with a format-14 subtable carrying a large nonDefaultUVSOffset, plus maxp.numGlyphs set to 65535 and a distinct hmtx advance per glyph.
- From content, call ctx.measureText("").
- The returned advance corresponds to a glyph id read from uninitialized heap. The gap's size and position are controlled by the chosen nonDefaultUVSOffset, so repeated measurements with different offsets can in principle be steered across a roughly 64 KB window; this was not exercised as a systematic scan here.
// content-side trigger, advance leaks the uninitialized glyph id
const w = ctx.measureText("").width; // maps into the uninitialized gap
// on a fixed build (154 / ESR 140.14+) the same measurement resolves to glyph id 0
Tools
- Python 3 with fontTools and Brotli to assemble the OTF/WOFF and splice in the crafted cmap (make_font.py).
- A small byte-level analyzer (analyze_leak.py) that verifies the crafted subtables directly from the file, independent of any font library.
- Firefox, reading the leaked glyph advance back through the canvas measureText API.
Impact
A web-controllable, repeatable disclosure of uninitialized process heap that can reveal pointers or secrets. OTS runs in-process ahead of platform-specific rasterization and is not among the sandboxed libraries, so the sanitizer-level gap this bug relies on is not tied to a specific rasterization backend; this was reproduced on the build captured above and was not separately tested across every platform.
Fix
Two independent changes, each targeting one of the defects described above: zero the newly-allocated range in the OTS output stream so no gap is ever uninitialized, and check the format-4 subtable length so the glyph lookup cannot bound against the rest of the cmap. Either change on its own would close the specific chain demonstrated here.
View on Mozilla Bugzilla Proof of concept codeMore research
Use-after-free in RasterImage surface discard
Firefox stores decoded image surfaces in a SurfaceCache keyed by a raw, non-owning pointer to the owning image, and notifies that…
Stored XSS in CometChat group messages
An authenticated user can inject a persistent JavaScript payload into a group chat message through the data.text parameter of the…