CVE-2026-85049
Overview
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
SkCachedDataalready released bydelete this. - MiraclePtr
- Chrome’s
raw_ptrheap-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.
delete this deterministically sequences unlock-then-free.Attack Path
- Allocate cached data
Drive Skia to create an
SkCachedDataand raise its refcount via cached image/bitmap or pixel-ref paths reachable from web content. - Drop the last reference
Trigger the final
internalUnrefsoinMutexUnrefreturnstrueand control enters thedelete thisbranch. - Free while locked
delete thisfrees the object while the temporaryAutoMutexWritableis still pending destruction at the end of the statement. - 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.
- 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
| Function | Change | Notes |
|---|---|---|
ifsrc/core/SkCachedData.cpp |
modified |
Files Changed
src/core/SkCachedData.cpp
Audit Directions
- Unlock-after-delete / temporary-lifetimeaudit any
delete thisor object-freeing branch that runs while an RAII lock guard, especially an unnamed temporary constructed in anif/expression, is still in scope; require the guard be explicitly scoped and destroyed before the free. - Self-owned synchronization primitivesflag mutexes or
raw_ptr/AutoMutex-style helpers embedded in the object they guard, since their teardown afterdelete thisreliably touches freed memory. - MiraclePtr as mitigationtreat 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.
Patch
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; }
Original Bug Report
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:
delete this;executes first, destroying the object and itsfMutex.~AutoMutexWritable()executes second, callingfCD->validate()andfCD->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;
}
}