Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read in Skia
DescriptionOut of bounds read in Skia
ComponentSkia
Bug ClassOOB
Tracker496206134
Fix commitf04d09d10759 (skia) +4/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-05

Files Changed

  • src/text/gpu/SkChromeRemoteGlyphCache.cpp
From f04d09d1075926a5e5ef0a52171c9c12043fab03 Mon Sep 17 00:00:00 2001
From: Florin Malita <fmalita@google.com>
Date: Fri, 27 Mar 2026 12:07:04 -0400
Subject: [PATCH] Use a local data copy for strike deserialization

The readStrikeData() input is volatile (shared memory) and untrusted.

To avoid time-of-check to time-of-use issues during deserialization,
always make a copy when transitioning to internal/non-volatile APIs.

This is similar to the other defensive copies used in Chromium's
cc/paint_op deserialization, e.g. [1].

[1] https://source.chromium.org/chromium/chromium/src/+/main:cc/paint/paint_op_reader.cc;drc=9c91b2494d4bf0a2d33b5985f7d1af79e72146f2;l=329

Bug: 496206134
Change-Id: I775d24b10ee7348b159016171ce044737f5bcbe0
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1197136
Commit-Queue: Florin Malita <fmalita@google.com>
Reviewed-by: Kaylee Lubick <kjlubick@google.com>
---

diff --git a/src/text/gpu/SkChromeRemoteGlyphCache.cpp b/src/text/gpu/SkChromeRemoteGlyphCache.cpp
index b0a37db..caf2869 100644
--- a/src/text/gpu/SkChromeRemoteGlyphCache.cpp
+++ b/src/text/gpu/SkChromeRemoteGlyphCache.cpp
@@ -632,9 +632,12 @@
     SkASSERT(memorySize != 0);
     SkASSERT(memory != nullptr);
 
+    // Use a local copy to defend against volatile memory TOCTOU issues during deserialization.
+    sk_sp<SkData> safeMemory = SkData::MakeWithCopy(const_cast<const void*>(memory), memorySize);
+
     // We do not need to set any SkDeserialProcs here because SkStrikeServerImpl::writeStrikeData
     // did not encode any SkImages.
-    SkReadBuffer buffer{const_cast<const void*>(memory), memorySize};
+    SkReadBuffer buffer{safeMemory->data(), safeMemory->size()};
     // Limit the kinds of effects that appear in a glyph's drawable (crbug.com/1442140):
     buffer.setAllowSkSL(false);
 
Loading diff…

Original Bug Report

reported by vm...@google.com

TOCTOU in Skia SkPathData::Make leads to Heap OOB Read in GPU process

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

Overview: A Time-of-Check Time-of-Use (TOCTOU) vulnerability exists in Skia’s path deserialization when reading font data from shared memory in the GPU process. A compromised renderer can mutate path verbs between validation and copying, creating a malformed SkPath. Iterating this path during rasterization causes a heap out-of-bounds read, potentially leaking sensitive cross-origin data.

Affected files:

  • third_party/skia/src/core/SkPathData.cpp
  • third_party/skia/src/core/SkPath_serial.cpp
  • third_party/skia/src/core/SkReadBuffer.cpp
  • third_party/skia/src/text/gpu/SkChromeRemoteGlyphCache.cpp
  • gpu/command_buffer/service/raster_decoder.cc

Estimated timestamp from git blame: 2025-12-23

Description

There is a potential Time-of-Check Time-of-Use (TOCTOU) vulnerability in Skia’s path deserialization logic when handling remote glyph cache data in the GPU process. This can lead to a heap out-of-bounds (OOB) read, allowing a compromised renderer to leak cross-origin GPU memory.

When a renderer sends font data to the GPU process via RasterDecoderImpl::DoRasterCHROMIUM, the data is passed via a shared memory buffer. The buffer is passed down as a volatile span to ServiceFontManager::Deserialize. However, Skia’s SkStrikeClient::readStrikeData explicitly casts away the volatile qualifier and wraps the shared memory directly in an SkReadBuffer:

// third_party/skia/src/text/gpu/SkChromeRemoteGlyphCache.cpp
SkReadBuffer buffer{const_cast<const void*>(memory), memorySize};

When deserializing an SkPath with the kVerbsAreStoredForward_Version format, SkPath::ReadFromMemory skips allocating temporary storage and instead obtains direct pointers into this shared memory for the points and verbs arrays.

These shared memory spans are passed to SkPathData::Make:

// third_party/skia/src/core/SkPathData.cpp
sk_sp<SkPathData> SkPathData::Make(SkSpan<const SkPoint> pts, SkSpan<const SkPathVerb> vbs, ...) {
    if (!valid_path_data(pts, vbs, conics)) {  // [1] Time of Check
        return nullptr;
    }
    return MakeNoCheck(pts, vbs, conics, {}, {}); // [2] Time of Use
}
  1. Time of Check: valid_path_data iterates over the vbs span (reading directly from shared memory) to calculate the expected number of points, verifying it matches the size of the pts array.
  2. Time of Use: If validation passes, MakeNoCheck allocates a new SkPathData object sized according to the original array lengths. It then calls SkSpanPriv::Copy to copy the verbs and points from the shared memory into the new allocation.

A compromised renderer can continuously mutate the verbs in shared memory. If the renderer changes a verb from kLine (requires 1 point) to kCubic (requires 3 points) after valid_path_data reads it but before MakeNoCheck copies it, the resulting SkPathData object will contain a kCubic verb but only have memory allocated for a kLine.

When the GPU process later iterates over this path (e.g., via SkPathEdgeIter or SkPath::RawIter) to draw it, the iterator will read past the bounds of the allocated fPoints array, treating adjacent heap memory as float coordinates. This geometry can then be read back via a canvas to leak sensitive data.

Suggested Steps to Trigger

(Note: These are potential steps as our setup does not have the ability to run code or verify a live exploit)

  1. A compromised renderer process allocates a shared memory buffer for font glyph serialization.
  2. The renderer crafts a serialized SkPath in this buffer using kVerbsAreStoredForward_Version, populating it with $N$ points and $N$ verbs (all set to kLine).
  3. The renderer issues a RasterDecoderImpl::DoRasterCHROMIUM command to the GPU process, pointing to this shared memory.
  4. While the GPU process begins deserialization, a racing thread in the renderer continuously overwrites the kLine (0x01) verbs in the shared memory with kCubic (0x04) verbs.
  5. If the race is won, the GPU process allocates an SkPathData object with space for $N$ points but copies in $N$ kCubic verbs.
  6. The renderer requests the GPU process to draw the glyph and reads back the resulting pixels. The out-of-bounds heap memory affects the drawn geometry, allowing the attacker to infer the leaked memory contents.

Suggested Fix

To prevent TOCTOU vulnerabilities, untrusted data from shared memory should be copied into secure, local memory before any validation occurs.

In SkPath::ReadFromMemory (or SkPathData::Make), the verbs and points arrays should be copied out of the SkReadBuffer (shared memory) into local buffers before calling valid_path_data. Alternatively, MakeNoCheck could be modified to perform the validation after copying the data, or a new Make variant could be introduced that safely copies and validates in a single pass.

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