Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Skia
DescriptionUse after free in Skia
ComponentSkia
Bug ClassUAF
Tracker500474409
Fix commitfcfe5975c945 (skia) +6/-6
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
if
src/ports/SkFontMgr_android_ndk.cpp
modified

Files Changed

  • src/ports/SkFontMgr_android_ndk.cpp
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,
Loading diff…

Original Bug Report

reported by vm...@google.com

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.cpp
  • third_party/skia/src/core/SkLRUCache.h
  • third_party/skia/src/base/SkTInternalLList.h
  • third_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 kUseAndroidNDKFontAPI feature 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.)

  1. Prime the Cache: Using JavaScript, request a few distinct font styles (e.g., variable fonts with different weights) on an OffscreenCanvas to populate the TypefaceCache.
  2. Setup Concurrency: Spawn a Web Worker that also has access to an OffscreenCanvas. Blink’s FontCache is thread-local, meaning font lookups on the worker do not acquire cross-thread Blink locks before calling down to Skia’s singleton font manager.
  3. 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 execute fLRU.remove(entry) and fLRU.addToHead(entry). This silently corrupts the list’s fPrev and fNext pointers.
  4. Force Cache Eviction: Flood the TypefaceCache with more than 64 new, unique font style requests. This exceeds the LRU cache capacity and triggers a background eviction loop.
  5. 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.

View on issue tracker