CVE-2026-13885
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsrc/ports/SkFontMgr_android_ndk.cpp |
modified |
Files Changed
src/ports/SkFontMgr_android_ndk.cpp
Patch
From fcfe5975c9456e808ef893b6c10ff8e09ec9c47f Mon Sep 17 00:00:00 2001
From: Kaylee Lubick <kjlubick@google.com>
Date: Thu, 28 May 2026 10:25:19 -0400
Subject: [PATCH] Use exclusive mutex in SkFontMgr_android_ndk.cpp
SkLRUCache::find() is mutating so a shared mutex doesn't actually
buy us anything here.
Change-Id: I9af6986d671c0399f4e3da7c3a6a7a698e95dd13
Bug: 500474409
Fixed: 500474409
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1248136
Commit-Queue: Alexis Cruz-Ayala <alexisdavidc@google.com>
Auto-Submit: Kaylee Lubick <kjlubick@google.com>
Reviewed-by: Alexis Cruz-Ayala <alexisdavidc@google.com>
---
diff --git a/src/ports/SkFontMgr_android_ndk.cpp b/src/ports/SkFontMgr_android_ndk.cpp
index 45088cf..7a62c40 100644
--- a/src/ports/SkFontMgr_android_ndk.cpp
+++ b/src/ports/SkFontMgr_android_ndk.cpp
@@ -15,9 +15,9 @@
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkFeatures.h"
#include "include/private/base/SkFloatingPoint.h"
+#include "include/private/base/SkMutex.h"
#include "include/private/base/SkTArray.h"
#include "include/private/base/SkTemplates.h"
-#include "src/base/SkSharedMutex.h"
#include "src/base/SkTSort.h"
#include "src/base/SkUTF.h"
#include "src/core/SkChecksum.h"
@@ -722,7 +722,7 @@
uint32_t fHash;
};
sk_sp<SkTypeface> find(const Request& request) {
- SkAutoSharedMutexShared lock(fMutex);
+ SkAutoMutexExclusive lock(fMutex); // SkLRUCache::find() is mutating
sk_sp<SkTypeface>* typeface = fRequests.find(request);
if (typeface) {
return *typeface;
@@ -730,7 +730,7 @@
return nullptr;
}
void add(const Request& request, sk_sp<SkTypeface> typeface) {
- SkAutoSharedMutexExclusive lock(fMutex);
+ SkAutoMutexExclusive lock(fMutex);
fRequests.insert_or_update(request, std::move(typeface));
}
@@ -773,7 +773,7 @@
uint32_t fHash;
};
sk_sp<SkTypeface> find(const Match& match) {
- SkAutoSharedMutexShared lock(fMutex);
+ SkAutoMutexExclusive lock(fMutex);
sk_sp<SkTypeface>* typeface = fMatches.find(match);
if (typeface) {
return *typeface;
@@ -781,13 +781,13 @@
return nullptr;
}
void add(Match&& match, sk_sp<SkTypeface> typeface) {
- SkAutoSharedMutexExclusive lock(fMutex);
+ SkAutoMutexExclusive lock(fMutex);
fMatches.insert_or_update(std::move(match), typeface);
}
private:
SkLRUCache<Request, sk_sp<SkTypeface>, Request::Hash> fRequests;
SkLRUCache<Match, sk_sp<SkTypeface>, Match::Hash> fMatches;
- SkSharedMutex fMutex;
+ SkMutex fMutex;
};
sk_sp<SkTypeface> adjustForStyle(sk_sp<SkTypeface_AndroidNDK>&& typeface, SkFontStyle style,
Original Bug Report
Potential UAF in SkFontMgr_AndroidNDK due to thread-unsafe SkLRUCache mutation
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the security team.
Overview: A data race in the Android NDK font manager’s TypefaceCache leads to internal linked list corruption and a Use-After-Free. The cache incorrectly uses a shared reader lock for an operation that mutates an LRU list. Concurrent font requests from Web Workers can corrupt the list pointers, enabling arbitrary memory corruption.
Affected files:
third_party/skia/src/ports/SkFontMgr_android_ndk.cppthird_party/skia/src/core/SkLRUCache.hthird_party/skia/src/base/SkTInternalLList.hthird_party/skia/src/base/SkSharedMutex.cpp
Estimated timestamp from git blame: 2025-08-22
Summary
A race condition exists in the TypefaceCache used by SkFontMgr_AndroidNDK (third_party/skia/src/ports/SkFontMgr_android_ndk.cpp). The find() methods in TypefaceCache acquire a shared (reader) lock (SkAutoSharedMutexShared) before calling SkLRUCache::find(). However, SkLRUCache::find() is a mutating operation: upon a cache hit, it moves the accessed entry to the head of its internal doubly linked list (SkTInternalLList) to maintain LRU ordering.
Because multiple threads can acquire the shared lock simultaneously, concurrent font lookups result in unsynchronized modifications to the fPrev and fNext pointers of the list’s nodes. This breaks the linked list invariants. When the cache subsequently exceeds its capacity and evicts old entries, the unlinking logic relies on these corrupted pointers, leading to a Write-After-Free or Use-After-Free.
Because Skia uses raw C++ pointers for its internal lists (Entry* instead of base::raw_ptr), this vulnerability is not mitigated by MiraclePtr (BackupRefPtr) and presents a primitive for Remote Code Execution (RCE) in the renderer sandbox.
Prerequisites
- Target running Android 16 (API level 36) or higher.
- The
kUseAndroidNDKFontAPIfeature flag must be enabled.
Potential Steps to Trigger
(Note: These are suggested steps to reproduce the issue. Our AI tooling does not currently have the ability to run code to verify a working Proof-of-Concept.)
- Prime the Cache: Using JavaScript, request a few distinct font styles (e.g., variable fonts with different weights) on an
OffscreenCanvasto populate theTypefaceCache. - Setup Concurrency: Spawn a Web Worker that also has access to an
OffscreenCanvas. Blink’sFontCacheis thread-local, meaning font lookups on the worker do not acquire cross-thread Blink locks before calling down to Skia’s singleton font manager. - Trigger the Race Condition: Coordinate the main thread and the Web Worker to render text using the previously cached fonts at the exact same time. Both threads enter
SkLRUCache::find(), read the same nodes, and concurrently executefLRU.remove(entry)andfLRU.addToHead(entry). This silently corrupts the list’sfPrevandfNextpointers. - Force Cache Eviction: Flood the
TypefaceCachewith more than 64 new, unique font style requests. This exceeds the LRU cache capacity and triggers a background eviction loop. - Exploit Write-After-Free: During eviction,
SkLRUCache::remove()attempts to unlink nodes using the corrupted pointers. This causes the program to dereference dangling pointers and write heap pointers into previously freed memory, which an attacker can reclaim and control via heap spraying.
Suggested Fix
In third_party/skia/src/ports/SkFontMgr_android_ndk.cpp, change the locking mechanism in both TypefaceCache::find(const Request& request) and TypefaceCache::find(const Match& match) from a shared lock to an exclusive lock, as the underlying cache lookup mutates the state:
// Change this:
// SkAutoSharedMutexShared lock(fMutex);
// To this:
SkAutoSharedMutexExclusive lock(fMutex);
Evaluated with Chrome root at commit: 137d451a126685dd5010e6609db9f6d4a78d8234
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. And please feel free to reach out to me directly if you have concerns or feedback on the project.