Low firefox Integer Overflow 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactlow
DescriptionInteger overflow in the Graphics component
ComponentGraphics
Bug ClassInteger Overflow
Tracker2028440
Fix commitf6cf28ce35c5 (firefox) +17/-6
CISA KEVNot listed
CreditedVladimir Meier
Disclosed2026-08-18

Background

`NVImage`
a Gecko image type in the layers subsystem holding NV12/NV21-style semi-planar YCbCr video frame data.
`CheckedInt32`
a Mozilla helper type that performs 32-bit integer arithmetic while tracking overflow, exposing isValid() and value().
`SourceSurface`
an abstract drawing surface produced from image data for compositing or readback.
Integer overflow
an arithmetic result that exceeds the range of its integer type, wrapping to an incorrect (often small) value.

Root Cause Analysis

Both NVImage::GetAsSourceSurface and NVImage::BuildSurfaceDescriptorBuffer computed the destination buffer size as ySize.height * mData.mYStride + cbcrSize.height * cbcrSize.width * 2 using plain int arithmetic. Because the YDataSize, CbCrDataSize, and stride values are attacker-influenced image dimensions, this multiply-and-add could overflow a signed 32-bit int, yielding a bufferLength far smaller than the data actually copied into the buffer. The invariant that the allocation passed to MakeUnique<uint8_t[]> must be at least as large as the bytes subsequently written was thus violated, allowing a heap buffer of undersized length to be populated with full-size plane data.

The fix recomputes bufferLength with CheckedInt32, checks isValid(), and bails out (return nullptr or NS_ERROR_FAILURE) before allocating when the size overflows, so the allocation is only performed with a correct, in-range length.

Key insight
The single core mistake was performing size arithmetic over untrusted image dimensions in a native int without overflow detection; the fix wraps the computation in CheckedInt32 and refuses to allocate when the result is not representable.

Attack Path

  1. Craft oversized image An attacker supplies a video/image frame whose YCbCr dimensions and stride are large enough that the byte-length computation wraps a signed 32-bit integer.
  2. Trigger surface conversion Content causes Gecko to convert the NVImage to a surface via GetAsSourceSurface or BuildSurfaceDescriptorBuffer.
  3. Undersized allocation The overflowed bufferLength produces a heap allocation much smaller than the real plane size.
  4. Heap overflow write Copying the Y and CbCr plane data into the too-small buffer writes past its bounds, corrupting adjacent heap memory.

Impact Assessment

An attacker gains an out-of-bounds heap write of image plane data in the process that performs graphics/image decoding for the affected NVImage. Exploitation requires the attacker to control image dimensions and to induce a surface conversion, and per the metadata the severity/impact is rated low.

Changed Functions

FunctionChangeNotes
if
gfx/layers/ImageContainer.cpp
modified

Files Changed

  • gfx/layers/ImageContainer.cpp

Audit Directions

  • Unchecked size arithmetic
    Search image/video/graphics code for width * height * stride-style products in plain int/size_t feeding allocation sizes, and require CheckedInt wrappers.
  • Allocation vs. copy mismatch
    Verify every MakeUnique<uint8_t[]>/buffer allocation derives its length from the same validated dimensions used by the subsequent copy loops.
  • Attacker-controlled dimensions
    Audit paths where decoded media dimensions and strides reach allocation math without prior range validation.
diff --git a/gfx/layers/ImageContainer.cpp b/gfx/layers/ImageContainer.cpp
index b0db3a93327..5e9b8316dda 100644
--- a/gfx/layers/ImageContainer.cpp
+++ b/gfx/layers/ImageContainer.cpp
@@ -1043,9 +1043,14 @@ already_AddRefed<SourceSurface> NVImage::GetAsSourceSurface() {
   // logics in PlanarYCbCrImage::GetAsSourceSurface().
   auto ySize = mData.YDataSize();
   auto cbcrSize = mData.CbCrDataSize();
-  const int bufferLength =
-      ySize.height * mData.mYStride + cbcrSize.height * cbcrSize.width * 2;
-  auto buffer = MakeUnique<uint8_t[]>(bufferLength);
+  auto bufferLength = CheckedInt32(ySize.height) * mData.mYStride +
+                      CheckedInt32(cbcrSize.height) * cbcrSize.width * 2;
+  if (!bufferLength.isValid()) {
+    NS_ERROR("Image buffer length exceeds integer limits.");
+    return nullptr;
+  }
+
+  auto buffer = MakeUnique<uint8_t[]>(bufferLength.value());
 
   Data aData = mData;
   aData.mCbCrStride = cbcrSize.width;
@@ -1118,9 +1123,15 @@ nsresult NVImage::BuildSurfaceDescriptorBuffer(
 
   RefPtr<gfx::DataSourceSurface> sourceSurface = mSourceSurface.Get();
   if (!sourceSurface) {
-    const int bufferLength =
-        ySize.height * mData.mYStride + cbcrSize.height * cbcrSize.width * 2;
-    buffer = MakeUnique<uint8_t[]>(bufferLength);
+    auto bufferLength = CheckedInt32(ySize.height) * mData.mYStride +
+                        CheckedInt32(cbcrSize.height) * cbcrSize.width * 2;
+
+    if (!bufferLength.isValid()) {
+      NS_ERROR("Image buffer length exceeds integer limits.");
+      return NS_ERROR_FAILURE;
+    }
+
+    buffer = MakeUnique<uint8_t[]>(bufferLength.value());
     aData.mYChannel = buffer.get();
 
     if (mData.mCbChannel < mData.mCrChannel) {  // NV12
Loading diff…