From 7c9ffcb347d4169fdca223d21cc62c2c0420db66 Mon Sep 17 00:00:00 2001
From: Kaylee Lubick <kjlubick@google.com>
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: https://issues.chromium.org/issues/513973560
Fixed: 513973560
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1239556
Reviewed-by: Thomas Smith <thomsmit@google.com>
---
 src/pathops/SkPathWriter.cpp | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)

--- a/src/pathops/SkPathWriter.cpp
+++ b/src/pathops/SkPathWriter.cpp
@@ -55,6 +55,7 @@
 }
 
 bool SkPathWriter::deferredLine(const SkOpPtT* pt) {
+    SkASSERT(pt);
     SkASSERT(fFirstPtT);
     SkASSERT(fDefer[0]);
     if (fDefer[0] == pt) {
@@ -77,6 +78,7 @@
 }
 
 void SkPathWriter::deferredMove(const SkOpPtT* pt) {
+    SkASSERT(pt);
     if (!fDefer[1]) {
         fFirstPtT = fDefer[0] = pt;
         return;
@@ -160,6 +162,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])) {
@@ -179,6 +182,7 @@
 }
 
 bool SkPathWriter::changedSlopes(const SkOpPtT* ptT) const {
+    SkASSERT(ptT);
     if (matchedLast(fDefer[0])) {
         return false;
     }
@@ -213,7 +217,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];
@@ -363,7 +375,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) {
