CVE-2026-79093
Overview
Background
- Text-clip mask painting
- Blink renders
-webkit-background-clip: text(and similar) by painting a mask in thePaintPhase::kTextClipphase;BoxFragmentPainter::PaintTextClipMaskbuilds a nestedPaintInfofor that pass. - `PaintInfo` / `PaintFlag`
- the per-paint context object;
PaintFlag::kPrivacyPreservingmarks a paint that must not emit content the caller is not allowed to see (e.g. cross-origin pixels), andPaintInfo::IsPrivacyPreserving()reports whether the current paint carries that flag. - Privacy-preserving paint paths
- capture/snapshot flows (element capture, cross-origin-restricted painting) run the paint tree with
kPrivacyPreservingset so downstream painters redact protected content. - Flag propagation invariant
- when a painter spawns a child
PaintInfofor a sub-pass, it must carry the parent’s privacy-preserving state forward, or the sub-pass silently paints without the restriction.
Root Cause Analysis
BoxFragmentPainter::PaintTextClipMask constructs a fresh PaintInfo for the kTextClip mask pass from the parent paint_info, copying the context, cull rect, phase and DescendantPaintingBlocked() state — but it did not propagate the parent’s privacy-preserving flag.
As a result, when the outer paint was privacy-preserving (a capture/snapshot that must not expose cross-origin content), the text-clip mask sub-pass ran with no such restriction, so painting the mask could emit content that the privacy-preserving paint was specifically meant to withhold — an incorrect-authorization / cross-origin disclosure condition.
The fix threads the flag through explicitly: the new PaintInfo is built with paint_info.IsPrivacyPreserving() ? PaintFlag::kPrivacyPreserving : PaintFlag::kNoFlag, so the mask pass inherits the same restriction as its parent. The change is defensive hardening of the paint pipeline against downstream cross-origin content ever reaching a text-clip mask.
PaintInfo spawned for the text-clip mask dropped the parent’s kPrivacyPreserving flag, so a privacy-preserving paint stopped being privacy-preserving inside the mask sub-pass; propagating the flag restores the invariant.Attack Path
- Enter a privacy-preserving paint
content reaches a capture/snapshot path (e.g. cross-origin-restricted element capture) that paints with
PaintFlag::kPrivacyPreservingset. - Target a text-clip element
the captured subtree contains an element using text-clip masking, so
BoxFragmentPainter::PaintTextClipMaskruns. - Flag is dropped
pre-fix, the mask
PaintInfois created without the privacy-preserving flag, so the mask pass paints unrestricted. - Cross-origin content exposed content that the privacy-preserving paint was meant to redact is painted into the mask output, disclosing it cross-origin.
Impact Assessment
kPrivacyPreserving paint over a text-clip-masked element; Chrome tracks it as incorrect authorization in Paint (high severity), and the fix is a defensive propagation that closes the gap before any downstream painter could leak cross-origin pixels.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/core/paint/box_fragment_painter.cc |
modified |
Files Changed
third_party/blink/renderer/core/paint/box_fragment_painter.cc
Audit Directions
- Child `PaintInfo` constructionaudit every site that builds a nested
PaintInfofor a sub-pass (masks, clips, filters, reflections) and confirm it forwardsIsPrivacyPreserving()and other security-relevant flags from the parent. - Privacy-preserving completenessenumerate the paint phases reachable under
kPrivacyPreservingand verify each honors the flag end-to-end, not just at the top-level entry. - Flag-drop on copyflag constructors/copies of context objects that carry security state (
PaintFlag, taint, sandbox bits) but re-initialize a subset of fields, silently losing the rest.
Patch
From 84661bdde8bb85cdeb99dafd1661f13e6949cabf Mon Sep 17 00:00:00 2001
From: Stephen Chenney <schenney@chromium.org>
Date: Fri, 24 Jul 2026 13:05:08 -0700
Subject: [PATCH] [HiC] Add privacy flag for text clip
Not really a bug since it's not an external resource, but it makes
the AI happy and prevents further bug reports or potential problems
if the code downstream ever paints cross origin content.
Bug: 523095011
Change-Id: Id6dea450cbea60a109dd67b1e2f53f8401e47015
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8139364
Commit-Queue: Philip Rogers <pdr@chromium.org>
Auto-Submit: Stephen Chenney <schenney@chromium.org>
Reviewed-by: Philip Rogers <pdr@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1668117}
---
diff --git a/third_party/blink/renderer/core/paint/box_fragment_painter.cc b/third_party/blink/renderer/core/paint/box_fragment_painter.cc
index 1bdfbf5b..65a81573 100644
--- a/third_party/blink/renderer/core/paint/box_fragment_painter.cc
+++ b/third_party/blink/renderer/core/paint/box_fragment_painter.cc
@@ -2294,9 +2294,11 @@
const gfx::Rect& mask_rect,
const PhysicalOffset& paint_offset,
bool object_has_multiple_boxes) {
- PaintInfo mask_paint_info(paint_info.context, CullRect(mask_rect),
- PaintPhase::kTextClip,
- paint_info.DescendantPaintingBlocked());
+ PaintInfo mask_paint_info(
+ paint_info.context, CullRect(mask_rect), PaintPhase::kTextClip,
+ paint_info.DescendantPaintingBlocked(),
+ paint_info.IsPrivacyPreserving() ? PaintFlag::kPrivacyPreserving
+ : PaintFlag::kNoFlag);
if (!object_has_multiple_boxes) {
PaintObject(mask_paint_info, paint_offset);
return;