Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Skia
DescriptionInsufficient validation of untrusted input in Skia
ComponentSkia
Bug ClassLogic Error
Tracker496526419
Fix commit4320748aa7d3 (skia) +23/-7
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-12

Changed Functions

FunctionChangeNotes
if
src/effects/SkTableMaskFilter.cpp
modified
if
src/utils/SkMultiPictureDocument.cpp
modified
for
src/utils/SkMultiPictureDocument.cpp
modified

Files Changed

  • src/effects/SkTableMaskFilter.cpp
  • src/utils/SkMultiPictureDocument.cpp
From 4320748aa7d3dc82b7064b23625a78d621f21963 Mon Sep 17 00:00:00 2001
From: Kaylee Lubick <kjlubick@google.com>
Date: Mon, 30 Mar 2026 08:44:58 -0700
Subject: [PATCH] Validate sizes in mskp reading and SkTableMaskFilter

The max dimension for mskps is approximately the sqrt of INT32_MAX
which felt "big enough"

Bug: b/496526419
Change-Id: I6850c28e18f7427d85ea0ea724c5d056a4682970
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1198616
Commit-Queue: Kaylee Lubick <kjlubick@google.com>
Reviewed-by: Jorge Betancourt <jmbetancourt@google.com>
---

diff --git a/src/effects/SkTableMaskFilter.cpp b/src/effects/SkTableMaskFilter.cpp
index d422ce0..ad42372 100644
--- a/src/effects/SkTableMaskFilter.cpp
+++ b/src/effects/SkTableMaskFilter.cpp
@@ -78,14 +78,22 @@
     if (src.fFormat != SkMask::kA8_Format) {
         return false;
     }
-
+    // SkAlign4 overflows when too close to INT32_MAX, so reject when too big.
+    constexpr int32_t kMaxWidth = 1 << 30;
+    if (src.fBounds.width() > kMaxWidth) {
+        return false;
+    }
     dst->bounds() = src.fBounds;
     dst->rowBytes() = SkAlign4(dst->fBounds.width());
     dst->format() = SkMask::kA8_Format;
     dst->image() = nullptr;
 
     if (src.fImage) {
-        dst->image() = SkMaskBuilder::AllocImage(dst->computeImageSize());
+        auto imgSize = dst->computeImageSize();
+        if (imgSize == 0) {
+            return false;
+        }
+        dst->image() = SkMaskBuilder::AllocImage(imgSize);
 
         const uint8_t* srcP = src.fImage;
         uint8_t* dstP = dst->image();
diff --git a/src/utils/SkMultiPictureDocument.cpp b/src/utils/SkMultiPictureDocument.cpp
index 5f327e6..72377d0 100644
--- a/src/utils/SkMultiPictureDocument.cpp
+++ b/src/utils/SkMultiPictureDocument.cpp
@@ -50,7 +50,9 @@
 
 static constexpr char kEndPage[] = "SkMultiPictureEndPage";
 
-const uint32_t kVersion = 2;
+static constexpr uint32_t kVersion = 2;
+
+static constexpr uint32_t kMaxDimension = 1 << 16;
 
 static SkSize join(const TArray<SkSize>& sizes) {
     SkSize joined = {0, 0};
@@ -169,28 +171,34 @@
         return 0;
     }
     uint32_t pageCount;
-    if (!src->readU32(&pageCount) || pageCount > INT_MAX) {
+    if (!src->readU32(&pageCount) || pageCount > (INT_MAX / sizeof(SkSize))) {
         return 0;
     }
     // leave stream position right here.
     return SkTo<int>(pageCount);
 }
 
-bool ReadPageSizes(SkStreamSeekable* stream,
+bool ReadPageSizes(SkStreamSeekable* src,
                    SkDocumentPage* dstArray,
                    int dstArrayCount) {
     if (!dstArray || dstArrayCount < 1) {
         return false;
     }
-    int pageCount = ReadPageCount(stream);
+    int pageCount = ReadPageCount(src);
     if (pageCount < 1 || pageCount != dstArrayCount) {
         return false;
     }
+    if (src->hasLength() && src->getLength() < (static_cast<size_t>(pageCount) * sizeof(SkSize))) {
+        return false;  // not enough memory to read all the sizes
+    }
     for (int i = 0; i < pageCount; ++i) {
         SkSize& s = dstArray[i].fSize;
-        if (sizeof(s) != stream->read(&s, sizeof(s))) {
+        if (sizeof(s) != src->read(&s, sizeof(s))) {
             return false;
         }
+        if (s.isEmpty() || s.width() >= kMaxDimension || s.height() >= kMaxDimension) {
+            return false;  // invalid sizes
+        }
     }
     // leave stream position right here.
     return true;
Loading diff…

Original Bug Report

reported by vm...@google.com

Unbounded page dimensions in SkMultiPictureDocument lead to DoS in PrintCompositor

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: SkMultiPictureDocument::ReadPageSizes deserializes page dimensions without checking for excessively large values. A compromised renderer can send a malicious MSKP file with huge page sizes, causing massive memory allocations and integer overflows in Skia, leading to a Denial of Service in the PrintCompositor process.

Affected files:

  • third_party/skia/src/utils/SkMultiPictureDocument.cpp
  • third_party/skia/src/pdf/SkPDFDocument.cpp
  • third_party/skia/src/core/SkDocument.cpp
  • components/services/print_compositor/print_compositor_impl.cc

Estimated timestamp from git blame: 2025-03-24

Summary

The SkMultiPictureDocument::ReadPageSizes function in Skia lacks upper-bound validation for page dimensions read from its input stream. This enables a compromised renderer to specify extremely large page sizes that are subsequently used by the PrintCompositor process. This issue leads to a Denial of Service via memory exhaustion and triggers integer overflows in downstream Skia components like mask filters.

Technical Details

When processing a SkMultiPictureDocument (MSKP) in the PrintCompositor utility process, the following data flow occurs:

  1. Deserialization: In third_party/skia/src/utils/SkMultiPictureDocument.cpp, the ReadPageSizes function reads SkSize values (floats) directly from the stream without any validation against upper bounds, infinity, or NaN.
  2. Page Initiation: These unvalidated sizes are passed through PrintCompositorImpl to SkDocument::beginPage in third_party/skia/src/core/SkDocument.cpp.
  3. PDF Generation: In third_party/skia/src/pdf/SkPDFDocument.cpp, the onBeginPage method multiplies the dimensions by fRasterScale. The resulting value is rounded to an integer via toRound(), which saturates to INT32_MAX. This results in an SkPDFDevice being initialized with massive bounds.
  4. Integer Overflows in Filters: If the attacker also includes a drawing command with an SkTableMaskFilter, the large device bounds propagate to SkPDFDevice::internalDrawPathWithFilter. The bounds are used to compute the size of masks. For example, in SkTableMaskFilterImpl::filterMask, the calculation dst->rowBytes() = SkAlign4(dst->fBounds.width()) can overflow int32_t, and dst->computeImageSize() returns 0 due to safeMul32 returning 0 for sizes > INT32_MAX. This leads to a 0-byte allocation followed by a massive linear out-of-bounds write of >2GB, causing an immediate segmentation fault.

Impact

An attacker with control over a renderer process can send a malicious MSKP via the DidPrintContentParams.metafile_data_region IPC. This reachability allows the attacker to reliably crash the sandboxed PrintCompositor utility process by triggering massive out-of-bounds writes (which are guaranteed to hit unmapped guard pages) or OOMs, resulting in a Denial of Service.

Reproduction Steps (Suggested)

  1. In a compromised renderer, generate an MSKP file.
  2. Add a page with SkSize set to dimensions that scale to approximately INT32_MAX (e.g., width = 1073741821, height = 2).
  3. Add a path to the page that fills the dimensions, and apply an SkTableMaskFilter to the paint.
  4. Send the MSKP via IPC to the PrintCompositor.
  5. Observe the PrintCompositor process crash due to an OOM or a segmentation fault when Skia attempts to allocate or write to the massive mask buffer.

Suggested Fix

In third_party/skia/src/utils/SkMultiPictureDocument.cpp, ReadPageSizes should validate the deserialized SkSize values. If the width or height is negative, non-finite, or exceeds a reasonable maximum document size, the function should return false to reject the malformed document.

Evaluated with Chrome root at commit: 0eb4855bda702feaaa8b899336664f97e3df88b8


Results 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. Please feel free to reach out to me if you have concerns or feedback.

View on issue tracker