Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactStack buffer overflow in Skia
DescriptionStack buffer overflow in Skia
ComponentSkia
Bug ClassOOB
Tracker497591594
Fix commit6e12daa9ab10 (skia) +10/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
for
src/core/SkEdgeClipper.cpp
modified
if
src/core/SkEdgeClipper.cpp
modified
switch
src/core/SkEdgeClipper.cpp
modified

Files Changed

  • src/core/SkEdgeClipper.cpp
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;
Loading diff…

Original Bug Report

reported by [email protected]

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.cpp
  • third_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:

  1. A vertical line on the left (appendVLine).
  2. The clipped cubic segment (appendCubic).
  3. A vertical line on the right (appendVLine).

Reviewing the append methods:

  • appendVLine sets 2 points in fPoints and increments the pointer by 2.
  • appendCubic sets 4 points in fPoints and 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.)

  1. An attacker hosts a malicious webpage.
  2. The webpage uses the Canvas 2D API (clip() and bezierCurveTo()) to construct a very tight clipping rectangle and a complex cubic Bezier curve.
  3. The curve coordinates are chosen specifically to exploit floating-point inaccuracies in SkChopCubicAtYExtrema and SkChopCubicAtXExtrema, forcing the curve to be chopped into 9 monotonic pieces.
  4. The curve is positioned such that at least 7 of these pieces span the width of the tight clipping rectangle.
  5. When Skia processes this path, SkEdgeClipper::clipCubic will overflow the fPoints and fVerbs stack buffers with the attacker-controlled floating-point coordinates.
  6. The overflow corrupts the stack frame, allowing the attacker to hijack the control flow (e.g., when the consume function pointer is called or when the function returns) and achieve RCE.

Suggested Fix

  1. Update the constants in SkEdgeClipper.h to reflect the correct worst-case scenario:
    enum {
        kMaxVerbs = 27,  // 9 pieces * (1 cubic + 2 lines)
        kMaxPoints = 72  // 9 pieces * (4 points + 2 + 2)
    };
    
  2. Introduce robust runtime bounds checking (e.g., using CHECK or base::CheckedNumeric equivalents if available in Skia) when appending points and verbs, rather than relying solely on SkASSERT.

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.

View on issue tracker