CVE-2025-0436
Overview
Background
- AAHairline ops
- Ganesh GPU-backend operations that render anti-aliased hairlines by tessellating paths into lines, quadratics, and conics before emitting vertices.
- `gather_lines_and_quads`
- a helper that walks a single path’s segments, appends them to shared buffers, and returns the number of quads contributed by that path.
- `SkSafeMath`
- a Skia utility that performs integer arithmetic while latching a sticky overflow flag queried via
SkSafeMath::ok. - Op combining
- Ganesh merges compatible draw ops into one, so
onPrepareDrawsmust sum primitive counts across every path instance folded into the op.
Root Cause Analysis
When multiple AAHairlineOp instances are chained together, AAHairlineOp::onPrepareDraws iterates over every folded path in fPaths and accumulates the per-path quad totals into the plain int accumulator quadCount, then computes int quadAndConicCount = conicCount + quadCount. Because these were ordinary signed-int additions with no bounds checking, a sufficiently large or crafted set of combined paths could push quadCount (and the subsequent conicCount + quadCount) past SK_MaxS32, producing signed integer overflow and a wrapped, undersized count. That corrupted count was then used to size vertex buffers and drive subsequent tessellation loops, so a too-small allocation would be indexed as if it were huge, yielding out-of-bounds writes.
The fix routes every accumulation through an SkSafeMath instance: the loop guard checks safeMath.ok(), each contribution is added with SkSafeMath::addInt, and the final conicCount + quadCount is likewise computed with addInt followed by an early return if the sticky overflow flag has tripped. This preserves the invariant that the primitive counts used for allocation and iteration never exceed the representable/allocatable range, aborting the draw instead of wrapping.
int arithmetic; the fix makes every addition overflow-aware via SkSafeMath::addInt and bails out with SkSafeMath::ok before any wrapped count can size a buffer.Attack Path
- Craft oversized geometry An attacker supplies content (e.g., canvas/SVG/WebGL-adjacent draws) that produces many hairline paths with a very large aggregate number of quads and conics.
- Force op combining
The paths are batched so Ganesh folds many
AAHairlineOpinstances together, fillingfPathsso their per-path quad counts are summed in oneonPrepareDrawspass. - Overflow the accumulator
The unchecked
quadCount += ...andconicCount + quadCountadditions exceedSK_MaxS32and wrap to a small or negative value. - Undersize the allocation
The wrapped
quadAndConicCountsizes the vertex buffer far smaller than the real primitive count. - Write out of bounds Tessellation then emits the true number of vertices into the undersized buffer, corrupting adjacent heap memory for a potential exploit primitive.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
forsrc/gpu/ganesh/ops/AAHairLinePathRenderer.cpp |
modified |
Files Changed
src/gpu/ganesh/ops/AAHairLinePathRenderer.cpp
Audit Directions
- Unchecked count accumulation across combined opsAudit other Ganesh op renderers that sum per-instance primitive/vertex counts over
fPaths-style lists with plainintand feed the result into allocation sizing. - Loop-summed geometry totalsLook for tessellation and path-gathering helpers whose returned counts are added in a loop without
SkSafeMath, especially where the total later multiplies a per-primitive vertex stride. - 32-bit sizing near SK_MaxS32 capsInspect code that compares accumulated counts against
SK_MaxS32-derived limits (likekMaxLines/kMaxQuadsAndConics) but computes those counts with non-saturating arithmetic.
Patch
From 8b030e47588af50f56ef380d81a17667baeb582b Mon Sep 17 00:00:00 2001
From: James Godfrey-Kittle <jamesgk@google.com>
Date: Tue, 17 Dec 2024 12:14:17 -0500
Subject: [PATCH] [ganesh] Avoid overflow when combining AAHairlineOps
Bug: b/382786791
Change-Id: I955d943015cce76f75221df9fab0897a6f22fe4b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/930577
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: James Godfrey-Kittle <jamesgk@google.com>
---
diff --git a/src/gpu/ganesh/ops/AAHairLinePathRenderer.cpp b/src/gpu/ganesh/ops/AAHairLinePathRenderer.cpp
index 3527904..2c04f31 100644
--- a/src/gpu/ganesh/ops/AAHairLinePathRenderer.cpp
+++ b/src/gpu/ganesh/ops/AAHairLinePathRenderer.cpp
@@ -27,6 +27,7 @@
#include "include/private/base/SkPoint_impl.h"
#include "include/private/base/SkTArray.h"
#include "include/private/gpu/ganesh/GrTypesPriv.h"
+#include "src/base/SkSafeMath.h"
#include "src/core/SkGeometry.h"
#include "src/core/SkMatrixPriv.h"
#include "src/core/SkPointPriv.h"
@@ -1219,16 +1220,28 @@
int instanceCount = fPaths.size();
bool convertConicsToQuads = !target->caps().shaderCaps()->fFloatIs32Bits;
- for (int i = 0; i < instanceCount; i++) {
+ SkSafeMath safeMath;
+ for (int i = 0; i < instanceCount && safeMath.ok(); i++) {
const PathData& args = fPaths[i];
- quadCount += gather_lines_and_quads(args.fPath, args.fViewMatrix, args.fDevClipBounds,
- args.fCapLength, convertConicsToQuads, &lines, &quads,
- &conics, &qSubdivs, &cWeights);
+ quadCount = safeMath.addInt(quadCount,
+ gather_lines_and_quads(args.fPath,
+ args.fViewMatrix,
+ args.fDevClipBounds,
+ args.fCapLength,
+ convertConicsToQuads,
+ &lines,
+ &quads,
+ &conics,
+ &qSubdivs,
+ &cWeights));
}
int lineCount = lines.size() / 2;
int conicCount = conics.size() / 3;
- int quadAndConicCount = conicCount + quadCount;
+ int quadAndConicCount = safeMath.addInt(conicCount, quadCount);
+ if (!safeMath.ok()) {
+ return;
+ }
static constexpr int kMaxLines = SK_MaxS32 / kLineSegNumVertices;
static constexpr int kMaxQuadsAndConics = SK_MaxS32 / kQuadNumVertices;
Original Bug Report
Security: Skia integer overflow (results in OOB) at AAHairlineOp::onPrepareDraws
Steps to reproduce the problem
- [optional] apply skia patch (only accelerate the computation to enhance bug reproduce)
- build skpbench with ubsan
- run script to generate path.skp and run ./skpbench –src poc/path.skp –config gles
Problem Description
In function AAHairlineOp::onPrepareDraws, the quadCount iterate every path and add gather_lines_and_quads, which can be controlled by fPath. by creating crafted skia path, we’re able to increase the gather_lines_and_quads results to 0xf0f4 in the poc. and there are no additional verification for the quadCount to prevent overflow.
int instanceCount = fPaths.size();
bool convertConicsToQuads = !target->caps().shaderCaps()->fFloatIs32Bits;
for (int i = 0; i < instanceCount; i++) {
const PathData& args = fPaths[i];
int quadCount += gather_lines_and_quads(args.fPath, args.fViewMatrix, args.fDevClipBounds,
args.fCapLength, convertConicsToQuads, &lines, &quads,
&conics, &qSubdivs, &cWeights);
// [1] gather_lines_and_quads can be super large (e.g. 0xf0f4)
// therefore the quadCount may overflow
}
Subsequently, the quadCount is used for allocation at [2-3], endup being used to allocate
buffer at [4]. Therefore, the OOB will happen at [5]
int quadAndConicCount = conicCount + quadCount; //[2]
static constexpr int kMaxLines = SK_MaxS32 / kLineSegNumVertices;
static constexpr int kMaxQuadsAndConics = SK_MaxS32 / kQuadNumVertices;
if (lineCount > kMaxLines || quadAndConicCount > kMaxQuadsAndConics) {
return;
}
int vertexCount = kQuadNumVertices * quadAndConicCount; // [3]
void* vertices = target->makeVertexSpace(sizeof(BezierVertex), vertexCount, &vertexBuffer,
&firstVertex); // [4]
if (!vertices || !quadsIndexBuffer) {
SkDebugf("Could not allocate vertices\n");
return;
}
BezierVertex* bezVerts = reinterpret_cast<BezierVertex*>(vertices);
int unsubdivQuadCnt = quads.size() / 3;
for (int i = 0; i < unsubdivQuadCnt; ++i) {
SkASSERT(qSubdivs[i] >= 0);
if (!quads[3*i].isFinite() || !quads[3*i+1].isFinite() || !quads[3*i+2].isFinite()) {
return;
}
add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &bezVerts); //[5]
}
Summary
Security: Skia integer overflow (results in OOB) at AAHairlineOp::onPrepareDraws
Custom Questions
Type of crash:
gpu
Reporter credit:
Han Zheng (HexHive)
Additional Data
Category: Security
Chrome Channel: Not sure
Regression: N/A