Medium firefox Integer Overflow 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionInteger overflow in the Graphics component
ComponentGraphics
Bug ClassInteger Overflow
Tracker2053327
Fix commitd41759d5d42f (firefox) +113/-7
CISA KEVNot listed
Credited5up3rh3i
Disclosed2026-08-18

Background

Cairo
the 2D graphics library bundled in Gecko (gfx/cairo) that Firefox uses for vector rendering, including printing surfaces.
`_cairo_win32_printing_surface_paint_linear_pattern`
the cairo function that paints a linear gradient onto a Windows GDI printing surface by building TRIVERTEX and GRADIENT_RECT arrays for GradientFill.
`TRIVERTEX` / `GRADIENT_RECT`
Win32 GDI structures describing gradient vertices and the rectangles between them that are filled during a GradientFill call.
Integer overflow
an arithmetic result that exceeds the width of its type and wraps around, here yielding an undersized allocation.

Root Cause Analysis

The vulnerable path computed the allocation sizes for the vertex and rectangle arrays as sizeof (TRIVERTEX) * (num_rects*2*num_ranges + 4) and sizeof (GRADIENT_RECT) * (num_rects*num_ranges + 2), where num_rects and num_ranges are int values derived from the gradient’s stop count and clip geometry. Because the multiplications were performed in int/size_t arithmetic, a pattern with many stops or wide ranges could overflow the size computation and produce a small allocation, while the subsequent fill loops iterate over the full un-wrapped num_ranges*num_rects count and write past the buffer. The invariant violated is that the allocated buffer must be large enough to hold every vertex and rectangle the loops will write.

The fix computes each size in a 64-bit cairo_uint64_t alloc_size, rejects the operation with CAIRO_INT_STATUS_UNSUPPORTED when the size exceeds SIZE_MAX, and additionally checks the _cairo_malloc results for NULL before use. It also restructures the function with a cleanup label so early failures free vert/rect (now NULL-initialized) and return the proper status instead of leaking or returning success.

Key insight
The single core mistake was performing the gradient array-size multiplications in native-width integer arithmetic without an overflow guard, so an attacker-influenced stop/range count could wrap the size and undersize the allocation; the fix moves the arithmetic into 64-bit cairo_uint64_t and bounds it against SIZE_MAX before allocating.

Attack Path

  1. Craft a gradient An attacker constructs a document (e.g. SVG/CSS) containing a linear gradient with a very large number of color stops.
  2. Route through printing The victim prints (or print-previews) the content on Windows, invoking the cairo Win32 printing surface path for the linear pattern.
  3. Overflow the size math num_rects*2*num_ranges + 4 (or the rect variant) overflows during the _cairo_malloc size computation, yielding a buffer far smaller than the loops require.
  4. Write out of bounds The vertex/rectangle initialization loops iterate over the full num_ranges*num_rects count and write TRIVERTEX/GRADIENT_RECT entries past the undersized allocation, corrupting heap memory.

Impact Assessment

An attacker gains a controlled heap buffer overflow (out-of-bounds write) in the process performing the print operation on Windows, enabling memory corruption that could be leveraged toward code execution. The precondition is Windows-specific and requires the victim to print or print-preview attacker-supplied gradient content, so it is not reachable by mere page rendering; Mozilla rates the impact moderate/medium.

Changed Functions

FunctionChangeNotes
if
gfx/cairo/cairo/src/win32/cairo-win32-printing-surface.c
modified
for
gfx/cairo/cairo/src/win32/cairo-win32-printing-surface.c
modified
for
gfx/cairo/patches/0042-win32-printing-linear-pattern.patch
modified

Files Changed

  • gfx/cairo/cairo/src/win32/cairo-win32-printing-surface.c
  • gfx/cairo/patches/0042-win32-printing-linear-pattern.patch

Audit Directions

  • Unchecked size multiplication
    Search other cairo/gfx allocation sites where _cairo_malloc/malloc size arguments multiply int counts (sizeof (T) * (a*b + c)) without 64-bit widening or overflow checks.
  • Attacker-influenced counts in Win32/GDI paths
    Review cairo-win32-* surface code where gradient stop counts, ranges, or clip-derived geometry drive loop bounds and buffer sizes, since these feed printing paths reachable from web content.
  • Return-success-on-error
    Look for functions that return 0/success after allocation or GDI failures without freeing intermediate buffers, which mask overflows and leak or use partially initialized state.
diff --git a/gfx/cairo/cairo/src/win32/cairo-win32-printing-surface.c b/gfx/cairo/cairo/src/win32/cairo-win32-printing-surface.c
index c5b5f5cab62..ab5680948e6 100644
--- a/gfx/cairo/cairo/src/win32/cairo-win32-printing-surface.c
+++ b/gfx/cairo/cairo/src/win32/cairo-win32-printing-surface.c
@@ -963,8 +963,8 @@ static cairo_int_status_t
 _cairo_win32_printing_surface_paint_linear_pattern (cairo_win32_printing_surface_t *surface,
                                                     cairo_linear_pattern_t *pattern)
 {
-    TRIVERTEX *vert;
-    GRADIENT_RECT *rect;
+    TRIVERTEX *vert = NULL;
+    GRADIENT_RECT *rect = NULL;
     RECT clip;
     XFORM xform;
     int i, num_stops;
@@ -974,6 +974,7 @@ _cairo_win32_printing_surface_paint_linear_pattern (cairo_win32_printing_surface
     int range_start, range_stop, num_ranges, num_rects, stop;
     int total_verts, total_rects;
     cairo_status_t status;
+    cairo_uint64_t alloc_size;
 
     extend = cairo_pattern_get_extend (&pattern->base.base);
     SaveDC (surface->win32.dc);
@@ -1006,7 +1007,8 @@ _cairo_win32_printing_surface_paint_linear_pattern (cairo_win32_printing_surface
 
     if (!SetWorldTransform (surface->win32.dc, &xform)) {
         fprintf (stderr, "%s:%s\n", __FUNCTION__, "SetWorldTransform");
-        return _cairo_error (CAIRO_STATUS_WIN32_GDI_ERROR);
+        status = _cairo_error (CAIRO_STATUS_WIN32_GDI_ERROR);
+        goto cleanup;
     }
 
     GetClipBox (surface->win32.dc, &clip);
@@ -1023,8 +1025,24 @@ _cairo_win32_printing_surface_paint_linear_pattern (cairo_win32_printing_surface
     num_rects = num_stops - 1;
 
     /* Add an extra four points and two rectangles for EXTEND_PAD */
-    vert = _cairo_malloc (sizeof (TRIVERTEX) * (num_rects*2*num_ranges + 4));
-    rect = _cairo_malloc (sizeof (GRADIENT_RECT) * (num_rects*num_ranges + 2));
+    alloc_size = sizeof (TRIVERTEX) * ((cairo_uint64_t)num_rects * 2 * (cairo_uint64_t)num_ranges + 4);
+    if (alloc_size > (cairo_uint64_t)SIZE_MAX) {
+        status = _cairo_error (CAIRO_INT_STATUS_UNSUPPORTED);
+        goto cleanup;
+    }
+    vert = _cairo_malloc (alloc_size);
+
+    alloc_size = sizeof (GRADIENT_RECT) * ((cairo_uint64_t)num_rects * (cairo_uint64_t)num_ranges + 2);
+    if (alloc_size > (cairo_uint64_t)SIZE_MAX) {
+        status = _cairo_error (CAIRO_INT_STATUS_UNSUPPORTED);
+        goto cleanup;
+    }
+    rect = _cairo_malloc (alloc_size);
+
+    if (!vert || !rect) {
+        status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
+        goto cleanup;
+    }
 
     for (i = 0; i < num_ranges*num_rects; i++) {
 	vert[i*2].y = (LONG) clip.top;
@@ -1098,14 +1116,15 @@ _cairo_win32_printing_surface_paint_linear_pattern (cairo_win32_printing_surface
 		       GRADIENT_FILL_RECT_H))
     {
         fprintf (stderr, "%s:%s\n", __FUNCTION__, "GradientFill");
-        return _cairo_error (CAIRO_STATUS_WIN32_GDI_ERROR);
+        status = _cairo_error (CAIRO_STATUS_WIN32_GDI_ERROR);
     }
 
+cleanup:
     free (rect);
     free (vert);
     RestoreDC (surface->win32.dc, -1);
 
-    return 0;
+    return status;
 }
 
 static cairo_int_status_t
diff --git a/gfx/cairo/patches/0042-win32-printing-linear-pattern.patch b/gfx/cairo/patches/0042-win32-printing-linear-pattern.patch
new file mode 100644
index 00000000000..cbc546ab8fb
--- /dev/null
+++ b/gfx/cairo/patches/0042-win32-printing-linear-pattern.patch
@@ -0,0 +1,87 @@
+# HG changeset patch
+# User Jonathan Kew <[email protected]>
+# Date 1783696818 -3600
+#      Fri Jul 10 16:20:18 2026 +0100
+# Node ID d4da049a5516d841cd873bcde3d097acf3ad7668
+# Parent  0b220ddc96596d5a21631503936a1f67608a309b
+Bug 2053327 - Check allocations in _cairo_win32_printing_surface_paint_linear_pattern. r=#gfx-reviewers
+
+Differential Revision: https://phabricator.services.mozilla.com/D311550
+
+diff --git a/gfx/cairo/cairo/src/win32/cairo-win32-printing-surface.c b/gfx/cairo/cairo/src/win32/cairo-win32-printing-surface.c
+--- a/gfx/cairo/cairo/src/win32/cairo-win32-printing-surface.c
++++ b/gfx/cairo/cairo/src/win32/cairo-win32-printing-surface.c
+@@ -963,8 +963,8 @@ static cairo_int_status_t
+ _cairo_win32_printing_surface_paint_linear_pattern (cairo_win32_printing_surface_t *surface,
+                                                     cairo_linear_pattern_t *pattern)
+ {
+-    TRIVERTEX *vert;
+-    GRADIENT_RECT *rect;
++    TRIVERTEX *vert = NULL;
++    GRADIENT_RECT *rect = NULL;
+     RECT clip;
+     XFORM xform;
+     int i, num_stops;
+@@ -974,6 +974,7 @@ _cairo_win32_printing_surface_paint_line
+     int range_start, range_stop, num_ranges, num_rects, stop;
+     int total_verts, total_rects;
+     cairo_status_t status;
++    cairo_uint64_t alloc_size;
+ 
+     extend = cairo_pattern_get_extend (&pattern->base.base);
+     SaveDC (surface->win32.dc);
+@@ -1006,7 +1007,8 @@ _cairo_win32_printing_surface_paint_line
+ 
+     if (!SetWorldTransform (surface->win32.dc, &xform)) {
+         fprintf (stderr, "%s:%s\n", __FUNCTION__, "SetWorldTransform");
+-        return _cairo_error (CAIRO_STATUS_WIN32_GDI_ERROR);
++        status = _cairo_error (CAIRO_STATUS_WIN32_GDI_ERROR);
++        goto cleanup;
+     }
+ 
+     GetClipBox (surface->win32.dc, &clip);
+@@ -1023,8 +1025,24 @@ _cairo_win32_printing_surface_paint_line
+     num_rects = num_stops - 1;
+ 
+     /* Add an extra four points and two rectangles for EXTEND_PAD */
+-    vert = _cairo_malloc (sizeof (TRIVERTEX) * (num_rects*2*num_ranges + 4));
+-    rect = _cairo_malloc (sizeof (GRADIENT_RECT) * (num_rects*num_ranges + 2));
++    alloc_size = sizeof (TRIVERTEX) * ((cairo_uint64_t)num_rects * 2 * (cairo_uint64_t)num_ranges + 4);
++    if (alloc_size > (cairo_uint64_t)SIZE_MAX) {
++        status = _cairo_error (CAIRO_INT_STATUS_UNSUPPORTED);
++        goto cleanup;
++    }
++    vert = _cairo_malloc (alloc_size);
++
++    alloc_size = sizeof (GRADIENT_RECT) * ((cairo_uint64_t)num_rects * (cairo_uint64_t)num_ranges + 2);
++    if (alloc_size > (cairo_uint64_t)SIZE_MAX) {
++        status = _cairo_error (CAIRO_INT_STATUS_UNSUPPORTED);
++        goto cleanup;
++    }
++    rect = _cairo_malloc (alloc_size);
++
++    if (!vert || !rect) {
++        status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
++        goto cleanup;
++    }
+ 
+     for (i = 0; i < num_ranges*num_rects; i++) {
+ 	vert[i*2].y = (LONG) clip.top;
+@@ -1098,14 +1116,15 @@ _cairo_win32_printing_surface_paint_line
+ 		       GRADIENT_FILL_RECT_H))
+     {
+         fprintf (stderr, "%s:%s\n", __FUNCTION__, "GradientFill");
+-        return _cairo_error (CAIRO_STATUS_WIN32_GDI_ERROR);
++        status = _cairo_error (CAIRO_STATUS_WIN32_GDI_ERROR);
+     }
+ 
++cleanup:
+     free (rect);
+     free (vert);
+     RestoreDC (surface->win32.dc, -1);
+ 
+-    return 0;
++    return status;
+ }
+ 
+ static cairo_int_status_t
Loading diff…