CVE-2026-10009
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsrc/pathops/SkPathWriter.cpp |
modified | |
forsrc/pathops/SkPathWriter.cpp |
modified |
Files Changed
src/pathops/SkPathWriter.cpp
Patch
From 7c9ffcb347d4169fdca223d21cc62c2c0420db66 Mon Sep 17 00:00:00 2001 From: Kaylee Lubick <[email protected]> Date: Tue, 19 May 2026 15:07:23 +0000 Subject: [PATCH] Avoid overflow and timeout in SkPathWriter::assemble I was unable to make a test case that caused an overflow and didn't timeout, but I think the possibility exists for both. This removes that, adds a few defensive asserts, and makes one assert actually checked at runtime, just to be safe. Change-Id: I4ce112624cb346ac7597466ac9c78402c5061e1c Bug: 513973560 Fixed: 513973560 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1239556 Reviewed-by: Thomas Smith <[email protected]> --- diff --git a/src/pathops/SkPathWriter.cpp b/src/pathops/SkPathWriter.cpp index 6577d9d..b374f9c 100644 --- a/src/pathops/SkPathWriter.cpp +++ b/src/pathops/SkPathWriter.cpp @@ -56,6 +56,7 @@ } bool SkPathWriter::deferredLine(const SkOpPtT* pt) { + SkASSERT(pt); SkASSERT(fFirstPtT); SkASSERT(fDefer[0]); if (fDefer[0] == pt) { @@ -78,6 +79,7 @@ } void SkPathWriter::deferredMove(const SkOpPtT* pt) { + SkASSERT(pt); if (!fDefer[1]) { fFirstPtT = fDefer[0] = pt; return; @@ -161,6 +163,7 @@ // if last point to be written matches the current path's first point, alter the // last to avoid writing a degenerate lineTo when the path is closed SkPoint SkPathWriter::update(const SkOpPtT* pt) { + SkASSERT(pt); if (!fDefer[1]) { this->moveTo(); } else if (!this->matchedLast(fDefer[0])) { @@ -180,6 +183,7 @@ } bool SkPathWriter::changedSlopes(const SkOpPtT* ptT) const { + SkASSERT(ptT); if (matchedLast(fDefer[0])) { return false; } @@ -214,7 +218,15 @@ SkOpPtT const* const* runs = fEndPtTs.begin(); // starts, ends of partial contours int endCount = fEndPtTs.size(); // all starts and ends SkASSERT(endCount > 0); - SkASSERT(endCount == fPartials.size() * 2); + SkASSERT(endCount == (int)fPartials.size() * 2); + + // Limit the number of partial contours to avoid O(N^2) complexity and integer overflows. + // 10,000 partial contours results in 20,000 ends and ~200,000,000 distance entries. + constexpr int kMaxPartialContours = 10000; + if (endCount > kMaxPartialContours * 2) { + return; + } + #if DEBUG_ASSEMBLE for (int index = 0; index < endCount; index += 2) { const SkOpPtT* eStart = runs[index]; @@ -364,7 +376,10 @@ if (forward) { next = contourPts.empty() ? SkPoint{0, 0} : contourPts.front(); } else { - SkASSERT(!contourPts.empty()); + if (contourPts.empty()) { + SkDEBUGFAIL("unexpected empty contour"); + return; + } next = contourPts.back(); } if (*prior != next) {
Original Bug Report
Heap memory corruption in PrintCompositor via Skia PathOps integer overflow
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: An integer overflow in Skia’s PathOps engine can be triggered by providing highly complex path geometry during PDF printing. This flaw leads to out-of-bounds memory access in the PrintCompositor utility process, potentially allowing for Site Isolation bypass or Remote Code Execution.
Affected files:
third_party/skia/src/utils/SkClipStackUtils.cppthird_party/skia/src/pdf/SkPDFDevice.cppthird_party/skia/src/pdf/SkPDFGraphicStackState.cppcomponents/services/print_compositor/print_compositor_impl.ccprinting/common/metafile_utils.cc
Estimated timestamp from git blame: 2019-12-20
Potential Root Cause
A vulnerability has been identified in the Skia PathOps engine, specifically within the SkPathWriter::assemble() function in third_party/skia/src/pathops/SkPathWriter.cpp. This function is responsible for joining “partial contours” (open path segments) that remain after complex boolean operations (like Simplify or Op).
The function calculates the number of potential endpoint pairs using 32-bit signed integer arithmetic:
const int entries = endCount * (endCount - 1) / 2;
If endCount (the number of endpoints) is approximately 46,342 or greater, the product endCount * (endCount - 1) overflows the maximum value for a 32-bit signed integer (INT_MAX). This results in entries becoming negative or incorrectly small.
Technical Details
When this overflow occurs, the following memory corruption primitives are triggered:
- Skipped Initializations: The loop that populates the distance and link arrays (
for (rIndex = 0; rIndex < entries; ++rIndex)) is skipped ifentriesis negative. This leaves thesLinkandeLinkarrays populated with their default value ofSK_MaxS32(2,147,483,647). - Massive Out-of-Bounds Read: In the subsequent assembly loop, the code uses these default values as indices:
eIndex = eLink[sIndex]. IfsIndexisSK_MaxS32, this performs an enormous out-of-bounds read from the heap. - Heap Memory Corruption: The resulting
eIndex(likely containing arbitrary heap data) is then used to index into thefPartialsarray (a collection ofSkPathBuilderobjects). The subsequent call tofPartials[rIndex].snapshot()results in the engine treating arbitrary heap memory as a validSkPathBuilderobject, leading to memory corruption. - Out-of-Bounds Write: Additionally, the cumulative row offset
rRowused to indexdistLookupcan also overflow, causing negative indices to be stored and later used to trigger out-of-bounds writes in thelinkOneorlinkTwoarrays.
Attack Vector and Reachability
An attacker who has already compromised a renderer process could potentially trigger this sink by:
- Generating a malicious
SkMultiPictureDocument(MSKP) containing thousands of self-intersectingSkPathobjects designed to create high complexity in PathOps. - Submitting this document to the browser via the printing flow, which forwards the content to the
PrintCompositorutility process for PDF conversion. - As the
PrintCompositorprocess replays the drawing commands, the complex geometry triggers the PathOps engine and the overflow inSkPathWriter::assemble().
Note that these are potential steps identified through code analysis; our current environment does not support executing proof-of-concept code.
Impact
The PrintCompositor service is a critical component for Site Isolation, as it aggregates content from multiple cross-origin renderer processes for printing. Compromising this process would allow an attacker to bypass Site Isolation and read cross-origin printed data.
Suggested Potential Fix
The calculations for entries and rRow in SkPathWriter::assemble should be updated to use 64-bit integers (int64_t) or Chromium’s base::CheckedNumeric to prevent overflows. Furthermore, a reasonable limit should be placed on the number of partial contours processed to mitigate both memory corruption and potential Denial of Service attacks via resource exhaustion.
Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
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.