CVE-2026-74988
Overview
Background
- `pixman`
- the low-level pixel-manipulation library bundled under
gfx/cairo/libpixmanthat Gecko uses for software rasterization of paths and trapezoids. - `RASTERIZE_EDGES`
- the pixman macro/template in
pixman-edge-imp.hthat walks trapezoid left/right edges scanline-by-scanline and fills spans between them. - `pixman_fixed_t`
- pixman’s 16.16 signed fixed-point number type, a 32-bit integer whose maximum representable value is
INT32_MAX. - `X_FRAC_FIRST`
- a macro producing the sub-pixel rounding offset added to an edge’s x-coordinate so that sample points on the line round north-west.
Root Cause Analysis
In the non-antialiased path of RASTERIZE_EDGES, the left and right edge coordinates lx and rx (both pixman_fixed_t) were adjusted with lx += X_FRAC_FIRST(1) - pixman_fixed_e and the analogous rx +=. Because lx/rx are 32-bit signed values, an edge coordinate near the top of the pixman_fixed_t range makes this addition overflow INT32_MAX, wrapping to a large negative value and violating the invariant that the rounded-up edge stays ordered and within the fixed-point range. The subsequent if (lx < 0) clip and the span computation then operate on a corrupted coordinate, so the fill can span the wrong range of pixels for the scanline.
The fix promotes the operands to int64_t, performs the add in 64-bit, and clamps the result to INT32_MAX with MIN before casting back to pixman_fixed_t, turning the overflowing add into a saturating one. This works because the coordinate can no longer wrap negative, keeping the edge within bounds so the clip and span logic behave correctly.
pixman_fixed_t and wrap to a negative value; the fix does the add in int64_t and saturates to INT32_MAX, guaranteeing the rounded edge stays in range and correctly ordered.Attack Path
- Craft an extreme geometry
Author web content (canvas, SVG, or CSS) that produces a trapezoid whose left or right edge x-coordinate is driven close to the
pixman_fixed_tmaximum. - Reach the rasterizer
Trigger software rasterization so the trapezoid is passed into
RASTERIZE_EDGESfor scanline filling. - Overflow the edge add
The
lx += X_FRAC_FIRST(1) - pixman_fixed_e(or therxvariant) overflows 32 bits and wraps to a negative coordinate. - Corrupt the span The clip and span computation use the wrapped coordinate, driving the fill outside the intended pixel range for the scanline and producing memory corruption in the pixel buffer.
Impact Assessment
pixman_fixed_t limit; per the advisory this is a presumed-exploitable memory-corruption defect rather than a demonstrated exploit.Files Changed
gfx/cairo/READMEgfx/cairo/libpixman/src/pixman-edge-imp.hgfx/cairo/pixman-edge-saturate.patch
Audit Directions
- 32-bit fixed-point arithmeticSearch pixman and cairo for
pixman_fixed_tadditions/subtractions that combine a coordinate with a rounding or fractional offset without range checks; they can overflow when inputs approachINT32_MAX. - Saturate-vs-wrap on geometryAudit rasterization inner loops (edge stepping, span clipping) for places where a computed coordinate is used before an ordering/bounds invariant is re-established, and confirm overflow is handled by widening or clamping.
- Attacker-influenced extreme coordinatesTrace how web-exposed graphics APIs (canvas, SVG, CSS transforms) can push path/trapezoid coordinates to fixed-point extremes and verify each downstream conversion to
pixman_fixed_tis range-safe.
Patch
diff --git a/gfx/cairo/README b/gfx/cairo/README index 0f733eebee9..16fba512702 100644 --- a/gfx/cairo/README +++ b/gfx/cairo/README @@ -43,3 +43,5 @@ pixman-interp.patch: use lower quality interpolation by default on mobile pixman-rename.patch: include pixman-rename.h for renaming of external symbols pixman-translation-round-in-wide.patch: Round translation distances in 48/16 fixpoint. + +pixman-edge-saturate.patch: Saturate when rounding up trapezoid edges diff --git a/gfx/cairo/libpixman/src/pixman-edge-imp.h b/gfx/cairo/libpixman/src/pixman-edge-imp.h index a4698eddb28..39e8d71d256 100644 --- a/gfx/cairo/libpixman/src/pixman-edge-imp.h +++ b/gfx/cairo/libpixman/src/pixman-edge-imp.h @@ -53,10 +53,13 @@ RASTERIZE_EDGES (pixman_image_t *image, * when the sample point lies exactly on the line, we round towards * north-west. * + * Use 64 bits to get a saturating add, in case lx or rx are near + * the limits of pixman_fixed_t. + * * (The AA case does a similar adjustment in RENDER_SAMPLES_X) */ - lx += X_FRAC_FIRST(1) - pixman_fixed_e; - rx += X_FRAC_FIRST(1) - pixman_fixed_e; + lx = (pixman_fixed_t) MIN ((int64_t) lx + (X_FRAC_FIRST(1) - pixman_fixed_e), INT32_MAX); + rx = (pixman_fixed_t) MIN ((int64_t) rx + (X_FRAC_FIRST(1) - pixman_fixed_e), INT32_MAX); #endif /* clip X */ if (lx < 0) diff --git a/gfx/cairo/pixman-edge-saturate.patch b/gfx/cairo/pixman-edge-saturate.patch new file mode 100644 index 00000000000..bb6fe0afa67 --- /dev/null +++ b/gfx/cairo/pixman-edge-saturate.patch @@ -0,0 +1,23 @@ +From: Jim Blandy <jimb@mozilla.com> +Subject: Saturate when rounding up trapezoid edges + +diff --git a/gfx/cairo/libpixman/src/pixman-edge-imp.h b/gfx/cairo/libpixman/src/pixman-edge-imp.h +index a4698eddb281..39e8d71d2568 100644 +--- a/gfx/cairo/libpixman/src/pixman-edge-imp.h ++++ b/gfx/cairo/libpixman/src/pixman-edge-imp.h +@@ -53,10 +53,13 @@ RASTERIZE_EDGES (pixman_image_t *image, + * when the sample point lies exactly on the line, we round towards + * north-west. + * ++ * Use 64 bits to get a saturating add, in case lx or rx are near ++ * the limits of pixman_fixed_t. ++ * + * (The AA case does a similar adjustment in RENDER_SAMPLES_X) + */ +- lx += X_FRAC_FIRST(1) - pixman_fixed_e; +- rx += X_FRAC_FIRST(1) - pixman_fixed_e; ++ lx = (pixman_fixed_t) MIN ((int64_t) lx + (X_FRAC_FIRST(1) - pixman_fixed_e), INT32_MAX); ++ rx = (pixman_fixed_t) MIN ((int64_t) rx + (X_FRAC_FIRST(1) - pixman_fixed_e), INT32_MAX); + #endif + /* clip X */ + if (lx < 0)