Overview

Low
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUninitialized resource in Skia
DescriptionUninitialized resource in Skia
ComponentSkia
Sub-componentGanesh
Bug ClassUninitialized Memory
Action tierwatch — ub-class only — no demonstrated corruption path
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker536166543
Fix commite0eb3d588ca5 (skia) +19/-1
CWECWE-908
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-07-18 (per vendor advisory)
Fix landed2026-07-22 (public git)
Disclosed2026-08-25
Patch gap34 days — fix public in source before the advisory

Background

Stencil buffer
A per-pixel auxiliary buffer whose bits Skia’s Ganesh backend partitions into a clip bit and user bits, used to mask and accumulate coverage during rendering.
`performStencilClearsAsDraws()`
A capability flag that reports a driver bug on which a plain stencil clear is unreliable, so the clear must instead be issued as an explicit full-surface draw op.
Stencil-then-cover path rendering
A path-fill technique that first writes coverage into the stencil buffer and then “covers” it with a draw that reads those stencil contents, meaning it depends on the stencil starting from a known (zeroed) state.
`GrUserStencilSettings`
The struct describing a stencil test/op, including which bits an op writes via its fWriteMask and which GrUserStencilOp it applies.

Root Cause Analysis

In SurfaceDrawContext.cpp, when performStencilClearsAsDraws() is true the code performed the mandatory initial stencil clear by calling internalStencilClear(nullptr, /* inside mask */ false). As the added comment states, that path lowers to gZeroStencilClipBit, whose fWriteMask == clipBit, so it zeroes only the clip bit and leaves the user bits untouched and therefore uninitialized. The invariant being violated is that a freshly targeted stencil buffer must be fully zeroed before a consumer reads it; here the user bits held undefined contents. A stencil-then-cover path renderer that reads those user bits on first use would then read uninitialized stencil memory, producing the “uninitialized resource” condition the advisory describes.

The fix records an explicit full-surface FillRectOp using kZeroAllStencilBits, which applies GrUserStencilOp::kZeroClipAndUserBits with a 0xffff write mask so that both the clip bit and all user bits are zeroed, restoring the known-zero invariant.

Key insight
The single mistake was treating a clip-bit-only clear (internalStencilClear, fWriteMask == clipBit) as a full initial stencil clear, leaving the user bits uninitialized; the fix issues a draw that zeroes clip and user bits (GrUserStencilOp::kZeroClipAndUserBits with a 0xffff mask) so nothing downstream reads undefined stencil state.

Attack Path

  1. Reach the draws-as-clear path Cause rendering on a device/driver where performStencilClearsAsDraws() returns true, so the initial stencil clear goes through the draw-op branch rather than a native clear.
  2. Leave user bits uninitialized The old internalStencilClear(nullptr, false) call zeroes only the clip bit, so the stencil buffer’s user bits retain undefined contents after the “clear.”
  3. Trigger a stencil-then-cover read Submit content whose fill is handled by a stencil-then-cover path renderer, whose cover step reads the stencil user bits on their first use, consuming the uninitialized values (as the patch comment states).
  4. Observe uninitialized-resource behavior The undefined stencil contents influence rendering; any escalation to a concrete read/write primitive, information disclosure, or code execution is not shown by the diff (inferred).

Impact Assessment

An attacker who can drive Skia rendering (for example via web content in the affected process context) on a driver where performStencilClearsAsDraws() is set can cause a stencil-then-cover path renderer to read uninitialized stencil user bits, matching the advisory’s “uninitialized resource in Skia” classification. The vendor advisory rates this low severity and does not report in-the-wild exploitation; NVD scores it CVSS 6.5. The advisory states no exploited-in-the-wild status and no severity here, so none is asserted. Any progression beyond the uninitialized read to a disclosure or memory-corruption primitive is (inferred) and not demonstrated by the patch.

Files Changed

  • src/gpu/ganesh/SurfaceDrawContext.cpp

Audit Directions

  • Partial clears mistaken for full clears
    Audit every use of internalStencilClear and gZeroStencilClipBit for cases that require a fully zeroed buffer but only zero the clip bit (fWriteMask == clipBit).
  • Capability-gated fallback paths
    Review other performStencilClearsAsDraws() (and similar driver-workaround) branches to confirm the draw-based fallback initializes exactly the same state as the native operation it replaces.
  • Read-before-init on GPU attachments
    Look for stencil/depth/render-target consumers such as stencil-then-cover path renderers that read buffer contents on first use and confirm every producer guarantees a fully initialized starting state.
From e0eb3d588ca59cbe70f1094b6f295d1275f929af Mon Sep 17 00:00:00 2001
From: Greg Daniel <[email protected]>
Date: Wed, 22 Jul 2026 10:51:38 -0400
Subject: [PATCH] [ganesh] Fully clear initial stencil bits when stencil clear is a draw

There is a driver bug where clearing stencil requires a manual draw
(performStencilClearsAsDraws). Previously, we called
internalStencilClear() which only zeroed the stencil clip bit
(fWriteMask == clipBit). However, the initial stencil clear must also
zero the user bits. Otherwise, stencil-then-cover path renderers might
read undefined stencil contents on their first use.

This change records a full-surface draw that zeroes both clip and user
bits when clearing as draw.

Bug: b/536166543

Change-Id: Ibedb70474a5aeabe8e8c9fc84e403faa256dadc1
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1300916
Commit-Queue: Greg Daniel <[email protected]>
Commit-Queue: Robert Phillips <[email protected]>
Reviewed-by: Robert Phillips <[email protected]>
---
diff --git a/src/gpu/ganesh/SurfaceDrawContext.cpp b/src/gpu/ganesh/SurfaceDrawContext.cpp
index a8339d2..c6caae4 100644
--- a/src/gpu/ganesh/SurfaceDrawContext.cpp
+++ b/src/gpu/ganesh/SurfaceDrawContext.cpp
@@ -830,7 +830,25 @@
         if (this->caps()->performStencilClearsAsDraws()) {
             // There is a driver bug with clearing stencil. We must use an op to manually clear the
             // stencil buffer before the op that required 'setNeedsStencil'.
-            this->internalStencilClear(nullptr, /* inside mask */ false);
+            // NOTE: internalStencilClear() only zeroes the *clip* bit (gZeroStencilClipBit lowers
+            // to fWriteMask == clipBit). The initial clear must also zero the user bits, otherwise
+            // stencil-then-cover path renderers read undefined stencil contents
+            constexpr static GrUserStencilSettings kZeroAllStencilBits(
+                    GrUserStencilSettings::StaticInit<
+                            0x0000,
+                            GrUserStencilTest::kAlways,
+                            0xffff,
+                            GrUserStencilOp::kZeroClipAndUserBits,
+                            GrUserStencilOp::kZeroClipAndUserBits,
+                            0xffff>());
+            GrPaint paint;
+            paint.setXPFactory(GrDisableColorXPFactory::Get());
+            SkRect rtRect =
+                    SkRect::Make(this->asSurfaceProxy()->backingStoreDimensions());
+            this->addDrawOp(nullptr,
+                            FillRectOp::MakeNonAARect(fContext, std::move(paint),
+                                                      SkMatrix::I(), rtRect,
+                                                      &kZeroAllStencilBits));
         } else {
             this->getOpsTask()->setInitialStencilContent(
                     OpsTask::StencilContent::kUserBitsCleared);
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.