CVE-2026-74964
Overview
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
TRIVERTEXandGRADIENT_RECTarrays forGradientFill. - `TRIVERTEX` / `GRADIENT_RECT`
- Win32 GDI structures describing gradient vertices and the rectangles between them that are filled during a
GradientFillcall. - 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.
cairo_uint64_t and bounds it against SIZE_MAX before allocating.Attack Path
- Craft a gradient An attacker constructs a document (e.g. SVG/CSS) containing a linear gradient with a very large number of color stops.
- Route through printing The victim prints (or print-previews) the content on Windows, invoking the cairo Win32 printing surface path for the linear pattern.
- Overflow the size math
num_rects*2*num_ranges + 4(or the rect variant) overflows during the_cairo_mallocsize computation, yielding a buffer far smaller than the loops require. - Write out of bounds
The vertex/rectangle initialization loops iterate over the full
num_ranges*num_rectscount and writeTRIVERTEX/GRADIENT_RECTentries past the undersized allocation, corrupting heap memory.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifgfx/cairo/cairo/src/win32/cairo-win32-printing-surface.c |
modified | |
forgfx/cairo/cairo/src/win32/cairo-win32-printing-surface.c |
modified | |
forgfx/cairo/patches/0042-win32-printing-linear-pattern.patch |
modified |
Files Changed
gfx/cairo/cairo/src/win32/cairo-win32-printing-surface.cgfx/cairo/patches/0042-win32-printing-linear-pattern.patch
Audit Directions
- Unchecked size multiplicationSearch other cairo/gfx allocation sites where
_cairo_malloc/mallocsize arguments multiplyintcounts (sizeof (T) * (a*b + c)) without 64-bit widening or overflow checks. - Attacker-influenced counts in Win32/GDI pathsReview
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-errorLook 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.
Patch
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 <jkew@mozilla.com>
+# 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