High firefox Info Disclosure 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionInformation disclosure in the Graphics: Text component
ComponentGraphics
Bug ClassInfo Disclosure
Tracker2057808
Fix commit67ef6f6c26b3 (firefox) +3/-1
CISA KEVNot listed
CreditedAbdulaziz Alasaiqah
Disclosed2026-08-18

Background

OTS (OpenType Sanitizer)
Firefox’s library that validates and rewrites downloadable font data into a sanitized copy before it reaches the font backend.
`gfxOTSExpandingMemoryStream`
an ots::OTSStream subclass that accumulates the sanitizer’s rewritten font bytes in a heap buffer that grows on demand.
`gfxOTSAllocator::Grow`
the allocator helper that malloc/realloc-style expands the stream’s backing buffer to a new length without zeroing the added region.
Uninitialized memory disclosure
a bug class where bytes never written by the program are exposed to an attacker, leaking prior heap contents.

Root Cause Analysis

The gfxOTSExpandingMemoryStream allocated its initial buffer via mAlloc.Grow(nullptr, mLength) and grew it via mAlloc.Grow(mPtr, newLength) in the write path, but neither call zeroed the freshly obtained bytes. Because an ots::OTSStream supports seeking (Seek) and writing at arbitrary offsets, a sanitized font can legitimately leave gaps between written table regions, and the buffer’s logical length can exceed the sum of bytes actually written. The violated invariant is that every byte of the stream buffer that becomes part of the emitted font must have a defined value; instead, unwritten gaps and any tail slack retained stale heap contents left over from previous allocations.

The fix restores the invariant by std::memset-ing the newly allocated region to zero — the whole initial block in the constructor and only the newly added [mLength, newLength) span on each grow — so any byte not subsequently overwritten by real font data reads back as zero. This closes the leak while avoiding redundant re-zeroing of already-initialized bytes on each growth.

Key insight
The single mistake was treating the grow allocator as if it returned zero-filled memory when it does not, so non-contiguously-written font output carried uninitialized heap bytes; the fix explicitly zeroes exactly the newly allocated bytes at allocation and at each expansion.

Attack Path

  1. Craft a sparse font An attacker builds a downloadable web font whose sanitized layout leaves gaps or trailing slack in the output stream that OTS never writes over.
  2. Trigger sanitization A victim page loads the font via @font-face, driving OTS to serialize the rewritten font into a gfxOTSExpandingMemoryStream.
  3. Capture stale heap bytes The gap and tail regions retain uninitialized heap memory from the process, which becomes embedded in the emitted sanitized font blob.
  4. Observe the leaked bytes The attacker recovers those bytes through an observable channel derived from the font data (for example rendered glyph metrics or shaping behavior driven by the leaked table content).

Impact Assessment

An attacker gains disclosure of uninitialized heap memory from the content/graphics process that performs font sanitization, potentially exposing pointers or secret data useful for defeating ASLR or building further exploits. The precondition is only that the victim loads an attacker-controlled web font, which any visited web page can request. No memory corruption or code execution follows directly from this diff; the demonstrated impact is information disclosure.

Files Changed

  • gfx/thebes/gfxOTSUtils.h

Audit Directions

  • Grow-without-zero allocators
    Audit every use of mAlloc.Grow, realloc, and custom bump/expand allocators feeding serialized output for the assumption that new capacity is zeroed.
  • Seekable output streams
    Review OTSStream/ots::OTSStream subclasses and other seekable writers where logical length can outrun written bytes, leaving unwritten gaps in exported buffers.
  • Sanitizer output boundaries
    Check that any buffer whose contents cross a trust boundary (font, image, or media data returned to renderers) is fully initialized before exposure, not merely up to the last write.
diff --git a/gfx/thebes/gfxOTSUtils.h b/gfx/thebes/gfxOTSUtils.h
index b0bb74339b0..b5ebb8db482 100644
--- a/gfx/thebes/gfxOTSUtils.h
+++ b/gfx/thebes/gfxOTSUtils.h
@@ -29,7 +29,8 @@ class gfxOTSExpandingMemoryStream : public ots::OTSStream {
   explicit gfxOTSExpandingMemoryStream(size_t initial,
                                        size_t limit = DEFAULT_LIMIT)
       : mLength(initial), mLimit(limit), mOff(0) {
-    mPtr = mAlloc.Grow(nullptr, mLength);
+    mPtr = mAlloc.Grow(nullptr, initial);
+    std::memset(mPtr, 0, initial);
   }
 
   ~gfxOTSExpandingMemoryStream() { mAlloc.Free(mPtr); }
@@ -59,6 +60,7 @@ class gfxOTSExpandingMemoryStream : public ots::OTSStream {
         newLength = mLimit;
       }
       mPtr = mAlloc.Grow(mPtr, newLength);
+      std::memset(static_cast<char*>(mPtr) + mLength, 0, newLength - mLength);
       mLength = newLength;
       return WriteRaw(data, length);
     }
Loading diff…