Uninitialized heap disclosure through a crafted web font
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.