Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Skia
DescriptionUse after free in Skia
ComponentSkia
Sub-componentInternals>Skia
Bug ClassUAF
Action tierpatch-now — corruption primitive, web-reachable in default config (confirmed) — upheld by independent review by AI
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.
Independent review by AIConfirmed
Reviewer notes: Root cause (lock-guard destructs after delete this, unlocking freed mutex) is confirmed by diff. But impact's 'groomable' and attacker-leverage claims are speculation unsupported by diff; the freed write is a constrained mutex-unlock.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker553345874
Fix commitd7c15613590b (skia) +6/-1
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-08-27 (per vendor advisory)
Fix landed2026-08-27 (public git)
Disclosed2026-09-03
Patch gap7 days — fix public in source before the advisory

Background

`SkCachedData`
Skia reference-counted object that owns an embedded mutex guarding its cache-tracking state during internalUnref.
`AutoMutexWritable`
RAII helper whose constructor locks this’s mutex and whose destructor unlocks it; as a temporary it lives until the end of the full statement.
Use-after-free (UAF)
accessing memory after it has been freed, here writing to a mutex embedded in a SkCachedData already released by delete this.
MiraclePtr
Chrome’s raw_ptr heap-safety mechanism whose rewrite pass surfaced this latent lifetime bug.

Root Cause Analysis

In the original SkCachedData::internalUnref, the AutoMutexWritable guard was constructed as an unnamed temporary directly inside the if condition, so C++ lifetime rules extended it to the end of the full expression statement. When inMutexUnref(fromCache) returned true — indicating the last reference had been dropped — control entered the branch and executed delete this, freeing the SkCachedData object along with its embedded mutex. Only after the enclosing statement completed did the temporary AutoMutexWritable destructor run, unlocking a mutex that now lived in freed memory. That unlock is a write into a released heap allocation, a classic UAF driven purely by temporary-object destruction ordering.

The fix captures the boolean decision into shouldDelete inside an inner scope, forcing the guard’s destructor (and the unlock) to run before delete this.

Key insight
The bug is entirely one of destructor ordering: the object is deleted while a stack-scoped lock guard over that same object is still alive, so the guard’s cleanup touches freed memory. Naming the guard and closing its scope before delete this deterministically sequences unlock-then-free.

Attack Path

  1. Allocate cached data Drive Skia to create an SkCachedData and raise its refcount via cached image/bitmap or pixel-ref paths reachable from web content.
  2. Drop the last reference Trigger the final internalUnref so inMutexUnref returns true and control enters the delete this branch.
  3. Free while locked delete this frees the object while the temporary AutoMutexWritable is still pending destruction at the end of the statement.
  4. Unlock into freed memory The temporary’s destructor unlocks the mutex embedded in the freed allocation; with heap grooming the attacker has already reoccupied that memory.
  5. Corrupt state The unlock write lands in attacker-controlled memory, providing a primitive toward further exploitation.

Impact Assessment

  • Use-after-free in the Skia graphics library, reachable from renderer-processed content, yielding a memory-corruption primitive via a write into freed memory.
  • Severity is high because the freed-then-written region is groomable, and the corrupting write is a mutex-unlock operation that an attacker can potentially leverage for control-flow or data corruption.

Changed Functions

FunctionChangeNotes
if
src/core/SkCachedData.cpp
modified

Files Changed

  • src/core/SkCachedData.cpp

Audit Directions

  • Unlock-after-delete / temporary-lifetime
    audit any delete this or object-freeing branch that runs while an RAII lock guard, especially an unnamed temporary constructed in an if/expression, is still in scope; require the guard be explicitly scoped and destroyed before the free.
  • Self-owned synchronization primitives
    flag mutexes or raw_ptr/AutoMutex-style helpers embedded in the object they guard, since their teardown after delete this reliably touches freed memory.
  • MiraclePtr as mitigation
    treat the raw_ptr/MiraclePtr rewrite as both a detector and a defense: it surfaced this latent UAF and can quarantine freed allocations, so prioritize enabling it across similar refcounted teardown paths.
From d7c15613590bfe9f9a40e82542c2873766a2e558 Mon Sep 17 00:00:00 2001
From: Arthur Sonzogni <[email protected]>
Date: Thu, 27 Aug 2026 09:47:10 +0000
Subject: [PATCH] Fix UAF in SkCachedData::internalUnref

Destroy AutoMutexWritable before calling delete this in
SkCachedData::internalUnref to avoid unlocking the mutex after the
object has been deleted.

This was found while trying to enable the MiraclePtr rewrite
that would protect "this" and cause deterministic termination.
Fixed: 553345874
Bug: 351867706
Doc: https://bit.ly/miracleptr-skia
Change-Id: I0a1d3aed3b3d41e4ed06505295a14028ff634788
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1340596
Reviewed-by: Michael Ludwig <[email protected]>
Commit-Queue: Arthur Sonzogni <[email protected]>
---
diff --git a/src/core/SkCachedData.cpp b/src/core/SkCachedData.cpp
index b48bee8..6df6d1d 100644
--- a/src/core/SkCachedData.cpp
+++ b/src/core/SkCachedData.cpp
@@ -65,7 +65,12 @@
 }
 
 void SkCachedData::internalUnref(bool fromCache) const {
-    if (AutoMutexWritable(this)->inMutexUnref(fromCache)) {
+    bool shouldDelete = false;
+    {
+        AutoMutexWritable amw(this);
+        shouldDelete = amw->inMutexUnref(fromCache);
+    }
+    if (shouldDelete) {
         // can't delete inside doInternalUnref, since it is locking a mutex (which we own)
         delete this;
     }
Loading diff…

Original Bug Report

reported by [email protected]

Use-After-Free in `SkCachedData::internalUnref()`

Use-After-Free in SkCachedData::internalUnref()

Problem

In src/core/SkCachedData.cpp:

void SkCachedData::internalUnref(bool fromCache) const {
    if (AutoMutexWritable(this)->inMutexUnref(fromCache)) {
        delete this;
    }
}

The temporary AutoMutexWritable(this) outlives the if body:

  1. delete this; executes first, destroying the object and its fMutex.
  2. ~AutoMutexWritable() executes second, calling fCD->validate() and fCD->fMutex.release() on freed memory.

With MiraclePtr enabled (raw_ptr<SkCachedData> fCD), step 2 deterministically crashes when accessing fCD after delete this.

Fix

Scope the lock to ensure destruction before delete this;:

void SkCachedData::internalUnref(bool fromCache) const {
    bool shouldDelete = false;
    {
        AutoMutexWritable amw(this);
        shouldDelete = amw->inMutexUnref(fromCache);
    }
    if (shouldDelete) {
        delete this;
    }
}
View on issue tracker