Moderate firefox Integer Overflow 🔧 Commit mapped

Overview

Moderate
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionInteger overflow in the Graphics: Text component
ComponentGraphics
Bug ClassInteger Overflow
Tracker2080581
Fix commit6e2c0fd5c765 (firefox) +5/-2
CISA KEVNot listed
Credited5up3rh3i
Disclosed2026-10-27

Background

`gfxTextRun`
A graphics/text object holding the shaped glyphs for a run of text. Its storage is a single allocation containing a fixed-size header/base (aSize) followed by a per-character glyph array.
`CompressedGlyph`
The fixed-size per-character glyph record. A text run of aLength characters needs aLength * sizeof(CompressedGlyph) bytes of glyph storage.
`AllocateStorageForTextRun(size_t aSize, uint32_t aLength)`
Allocates aSize + aLength * sizeof(CompressedGlyph) bytes with malloc, returning nullptr on failure (rather than throwing) because “web content can create huge runs.”
`CheckedInt<T>`
Mozilla’s overflow-checked integer type (mozilla/CheckedInt.h); arithmetic sets an invalid flag on overflow, checked via isValid().
`aLength` is `uint32_t`
Attacker-influenced (text length); multiplying it by sizeof(CompressedGlyph) and adding aSize can overflow the size_t byte count.

Root Cause Analysis

gfxTextRun::AllocateStorageForTextRun in gfx/thebes/gfxTextRun.cpp computed its allocation size as aSize + aLength * sizeof(CompressedGlyph) directly in native integer arithmetic. Because aLength is a uint32_t derived from (attacker-controllable) text length and sizeof(CompressedGlyph) is a multi-byte constant, the product aLength * sizeof(CompressedGlyph) (and its sum with aSize) can overflow size_t, wrapping to a small value. malloc then returns a buffer far smaller than the run actually requires, while the caller proceeds to populate glyph storage for all aLength characters — a classic undersized-allocation leading to heap out-of-bounds writes. The companion memset that zero-initializes the glyph region compounded this by using the same unchecked aLength * sizeof(CompressedGlyph) length.

The patch introduces CheckedInt<size_t> storageSize = CheckedInt<size_t>(aLength) * sizeof(CompressedGlyph) + aSize; and allocates only if storageSize.isValid() (otherwise malloc is skipped and nullptr is returned, matching the existing graceful-failure contract). The subsequent memset is rewritten to use storageSize.value() - aSize, so the zeroed length is derived from the same validated total rather than recomputed with the overflow-prone expression. The advisory’s “integer overflow in Graphics: Text” matches the diff exactly.

Key insight
The text-run storage size was computed as aSize + aLength * sizeof(CompressedGlyph) without overflow checking; a large aLength wraps the size_t byte count, so malloc returns an undersized buffer that the glyph-filling code then overruns.

Attack Path

  1. Huge text run Web content creates a text run with a very large character length, driving aLength toward values where aLength * sizeof(CompressedGlyph) (+ aSize) overflows size_t. (reachability via layout/text APIs inferred)
  2. Overflowed size Pre-patch, the native multiply/add wraps to a small value.
  3. Undersized allocation malloc succeeds and returns a buffer much smaller than the run needs; the non-null result passes the if (!storage) check.
  4. Heap overflow Code that initializes/fills aLength glyphs (and the original memset of aLength * sizeof(CompressedGlyph)) writes past the small allocation.
  5. Primitive Controlled heap out-of-bounds write adjacent to the undersized text-run buffer; with grooming this is a memory-corruption primitive rather than a mere crash. (exploit strength inferred)

Impact Assessment

The code is in the content process’s graphics/text layer, reachable by ordinary web content that lays out large amounts of text; no special privilege is required. The diff proves an integer overflow in the allocation-size computation that yields an undersized heap buffer, with the glyph-filling path (and the original memset) writing beyond it — a heap out-of-bounds write. Practical exploitation is gated on allocating a run large enough to wrap size_t and on heap layout. The advisory rates it moderate and labels it an integer overflow, consistent with the diff.

Changed Functions

FunctionChangeNotes
if
gfx/thebes/gfxTextRun.cpp
modified

Files Changed

  • gfx/thebes/gfxTextRun.cpp

Audit Directions

  • Unchecked size arithmetic before `malloc`
    Audit gfx/thebes and other text/layout allocators for malloc/realloc sizes computed as count * elementSize (+ header) from web-controllable counts without CheckedInt.
  • `memset`/`memcpy` lengths recomputed from the same inputs
    Look for buffer-fill operations whose length is recomputed with the overflow-prone expression rather than derived from the validated allocation size.
  • Other `CompressedGlyph`/glyph-array consumers
    Review code that indexes or fills glyph storage by aLength for assumptions that the backing allocation actually holds aLength records.
diff --git a/gfx/thebes/gfxTextRun.cpp b/gfx/thebes/gfxTextRun.cpp
index 39153aa9bd5..c69528870c1 100644
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -14,6 +14,7 @@
 #include "gfxHarfBuzzShaper.h"
 #include "gfxPlatformFontList.h"
 #include "gfxUserFontSet.h"
+#include "mozilla/CheckedInt.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MruCache.h"
@@ -128,7 +129,9 @@ bool gfxTextRun::NeedsGlyphExtents() const {
 void* gfxTextRun::AllocateStorageForTextRun(size_t aSize, uint32_t aLength) {
   // Allocate the storage we need, returning nullptr on failure rather than
   // throwing an exception (because web content can create huge runs).
-  void* storage = malloc(aSize + aLength * sizeof(CompressedGlyph));
+  CheckedInt<size_t> storageSize =
+      CheckedInt<size_t>(aLength) * sizeof(CompressedGlyph) + aSize;
+  void* storage = storageSize.isValid() ? malloc(storageSize.value()) : nullptr;
   if (!storage) {
     NS_WARNING("failed to allocate storage for text run!");
     return nullptr;
@@ -136,7 +139,7 @@ void* gfxTextRun::AllocateStorageForTextRun(size_t aSize, uint32_t aLength) {
 
   // Initialize the glyph storage (beyond aSize) to zero
   memset(reinterpret_cast<char*>(storage) + aSize, 0,
-         aLength * sizeof(CompressedGlyph));
+         storageSize.value() - aSize);
 
   return storage;
 }
Loading diff…