CVE-2026-11024
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forsrc/core/SkEdgeClipper.cpp |
modified | |
ifsrc/core/SkEdgeClipper.cpp |
modified | |
switchsrc/core/SkEdgeClipper.cpp |
modified |
Files Changed
src/core/SkEdgeClipper.cpp
Patch
From 6e12daa9ab106dadad7cde8033f64c44917e19d3 Mon Sep 17 00:00:00 2001 From: Bryan Enrique Gonzalez Velez <[email protected]> Date: Wed, 08 Apr 2026 15:02:10 +0000 Subject: [PATCH] [Project-Fortify] Increase SkEdgeClipper buffer sizes and add safety checks This is a quick fix for a potential security issue. This change increases the internal buffer sizes for SkEdgeClipper to more robustly handle complex path segments, especially cubics that might be split into many pieces during clipping. Additionally, added SkASSERT_RELEASE bounds checks to prevent memory corruption in release builds. Project-Fortify (Internal only) go/code-terracotta-review-explainer Bug: 497591594 Change-Id: I867bff37d712578f24f413f64d4a41fee830de73 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1199759 Reviewed-by: Eric Boren <[email protected]> Commit-Queue: Bryan Enrique Gonzalez <[email protected]> --- diff --git a/src/core/SkEdgeClipper.cpp b/src/core/SkEdgeClipper.cpp index 7a09ca3..1702c13 100644 --- a/src/core/SkEdgeClipper.cpp +++ b/src/core/SkEdgeClipper.cpp @@ -9,6 +9,7 @@ #include "include/core/SkRect.h" #include "include/core/SkTypes.h" +#include "include/private/base/SkAssert.h" #include "include/private/base/SkMacros.h" #include "src/core/SkGeometry.h" #include "src/core/SkLineClipper.h" @@ -237,8 +238,8 @@ int countX = SkChopQuadAtXExtrema(&monoY[y * 2], monoX); for (int x = 0; x <= countX; x++) { this->clipMonoQuad(&monoX[x * 2], clip); - SkASSERT(fCurrVerb - fVerbs < kMaxVerbs); - SkASSERT(fCurrPoint - fPoints <= kMaxPoints); + SkASSERT_RELEASE(fCurrVerb - fVerbs < kMaxVerbs); + SkASSERT_RELEASE(fCurrPoint - fPoints <= kMaxPoints); } } } @@ -456,6 +457,7 @@ void SkEdgeClipper::appendLine(SkPoint p0, SkPoint p1) { *fCurrVerb++ = SkPathVerb::kLine; + SkASSERT_RELEASE(fCurrPoint + 2 - fPoints <= kMaxPoints); fCurrPoint[0] = p0; fCurrPoint[1] = p1; fCurrPoint += 2; @@ -468,6 +470,7 @@ using std::swap; swap(y0, y1); } + SkASSERT_RELEASE(fCurrPoint + 2 - fPoints <= kMaxPoints); fCurrPoint[0].set(x, y0); fCurrPoint[1].set(x, y1); fCurrPoint += 2; @@ -476,6 +479,7 @@ void SkEdgeClipper::appendQuad(const SkPoint pts[3], bool reverse) { *fCurrVerb++ = SkPathVerb::kQuad; + SkASSERT_RELEASE(fCurrPoint + 3 - fPoints <= kMaxPoints); if (reverse) { fCurrPoint[0] = pts[2]; fCurrPoint[2] = pts[0]; @@ -490,6 +494,7 @@ void SkEdgeClipper::appendCubic(const SkPoint pts[4], bool reverse) { *fCurrVerb++ = SkPathVerb::kCubic; + SkASSERT_RELEASE(fCurrPoint + 4 - fPoints <= kMaxPoints); if (reverse) { for (int i = 0; i < 4; i++) { fCurrPoint[i] = pts[3 - i]; @@ -509,14 +514,17 @@ auto verb = *fCurrVerb++; switch (verb) { case SkPathVerb::kLine: + SkASSERT_RELEASE(fCurrPoint + 2 - fPoints <= kMaxPoints); memcpy(pts, fCurrPoint, 2 * sizeof(SkPoint)); fCurrPoint += 2; break; case SkPathVerb::kQuad: + SkASSERT_RELEASE(fCurrPoint + 3 - fPoints <= kMaxPoints); memcpy(pts, fCurrPoint, 3 * sizeof(SkPoint)); fCurrPoint += 3; break; case SkPathVerb::kCubic: + SkASSERT_RELEASE(fCurrPoint + 4 - fPoints <= kMaxPoints); memcpy(pts, fCurrPoint, 4 * sizeof(SkPoint)); fCurrPoint += 4; break;
Original Bug Report
Potential stack buffer overflow in SkEdgeClipper::clipCubic due to undersized buffers
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: Skia’s SkEdgeClipper allocates fixed-size stack buffers to store clipped path segments. The sizing of these buffers relies on incorrect arithmetic regarding the maximum number of points and verbs a chopped cubic can produce. A carefully constructed cubic Bezier curve that generates the maximum number of pieces spanning the clip rectangle can overflow these stack buffers, potentially leading to arbitrary code execution in the renderer process.
Affected files:
third_party/skia/src/core/SkEdgeClipper.cppthird_party/skia/src/core/SkEdgeClipper.h
Estimated timestamp from git blame: 2017-03-22
Vulnerability Details
In Skia’s path rendering, the SkEdgeClipper class is responsible for clipping paths against a bounding rectangle. When clipping a cubic Bezier curve via SkEdgeClipper::ClipPath -> clipCubic, the curve is first chopped into monotonic pieces at its Y and X extrema. The results of clipping these pieces are stored in stack-allocated arrays within the SkEdgeClipper object:
// third_party/skia/src/core/SkEdgeClipper.h
enum {
kMaxVerbs = 18, // max curvature in X and Y split cubic into 9 pieces, * (line + cubic)
kMaxPoints = 54 // 2 lines + 1 cubic require 6 points; times 9 pieces
};
SkPoint fPoints[kMaxPoints];
SkPathVerb fVerbs[kMaxVerbs];
The sizing of these buffers relies on a flawed arithmetic assumption. The developer’s comment assumes that a monotonic piece spanning the clip boundaries requires 6 points (“2 lines + 1 cubic require 6 points”).
However, looking at the implementation in SkEdgeClipper::clipMonoCubic, if a monotonic piece spans both the left and right edges of the clip rectangle, it generates three segments:
- A vertical line on the left (
appendVLine). - The clipped cubic segment (
appendCubic). - A vertical line on the right (
appendVLine).
Reviewing the append methods:
appendVLinesets 2 points infPointsand increments the pointer by 2.appendCubicsets 4 points infPointsand increments the pointer by 4.
Therefore, a single piece spanning the clip boundary actually writes 8 points (2 + 4 + 2) and 3 verbs, not 6 points and 2 verbs.
If floating-point calculations during curve chopping result in the worst-case scenario of 9 pieces (as anticipated by the developer), and a sufficient number of these pieces span the clip boundaries, the total required capacity is 72 points and 27 verbs. This exceeds the capacities of fPoints[54] and fVerbs[18].
When clipCubic processes these pieces in a loop, it writes the points to fCurrPoint. The only bounds checks present are SkASSERT(fCurrPoint - fPoints <= kMaxPoints), which are compiled out in Release builds. As a result, the code will write past the end of fPoints, overwrite the adjacent fVerbs array, and continue to write out-of-bounds into the stack frame of SkEdgeClipper::ClipPath.
Impact and Reachability
Because SkEdgeClipper clipper is allocated on the stack in SkEdgeClipper::ClipPath, overflowing fPoints and fVerbs results in a stack buffer overflow. An attacker controlling the coordinates of the path (e.g., via Canvas 2D bezierCurveTo or SVG paths) can potentially control the floating-point values written out of bounds.
By overwriting adjacent stack variables (such as the consume function pointer passed to ClipPath or the saved return address), this vulnerability can potentially be exploited to achieve Remote Code Execution (RCE) in the sandboxed renderer process.
Potential Attack Steps
(Note: These are suggested steps based on static analysis; a working PoC has not been developed yet.)
- An attacker hosts a malicious webpage.
- The webpage uses the Canvas 2D API (
clip()andbezierCurveTo()) to construct a very tight clipping rectangle and a complex cubic Bezier curve. - The curve coordinates are chosen specifically to exploit floating-point inaccuracies in
SkChopCubicAtYExtremaandSkChopCubicAtXExtrema, forcing the curve to be chopped into 9 monotonic pieces. - The curve is positioned such that at least 7 of these pieces span the width of the tight clipping rectangle.
- When Skia processes this path,
SkEdgeClipper::clipCubicwill overflow thefPointsandfVerbsstack buffers with the attacker-controlled floating-point coordinates. - The overflow corrupts the stack frame, allowing the attacker to hijack the control flow (e.g., when the
consumefunction pointer is called or when the function returns) and achieve RCE.
Suggested Fix
- Update the constants in
SkEdgeClipper.hto reflect the correct worst-case scenario:enum { kMaxVerbs = 27, // 9 pieces * (1 cubic + 2 lines) kMaxPoints = 72 // 9 pieces * (4 points + 2 + 2) }; - Introduce robust runtime bounds checking (e.g., using
CHECKorbase::CheckedNumericequivalents if available in Skia) when appending points and verbs, rather than relying solely onSkASSERT.
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.