CVE-2026-106377
Overview
Fix not yet public
Original Bug Report
Browser Process UAF / Heap OOB Write via FontConfig Race Condition on ChromeOS
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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A potential race condition exists on ChromeOS between the browser UI thread registering language pack fonts and background ThreadPool threads executing FontService IPC queries. Because modern FontConfig versions are treated as thread-safe, no synchronization lock is acquired, allowing concurrent modification of the global application font set. This race can potentially result in a Use-After-Free (UAF) or a Heap Out-Of-Bounds (OOB) write in the browser process.
Affected files:
chrome/browser/ash/language_packs/language_pack_font_service.cccontent/browser/font_service.ccui/gfx/linux/fontconfig_util.ccthird_party/fontconfig/src/src/fcmatch.cthird_party/fontconfig/src/src/fcfs.cthird_party/fontconfig/src/src/fccfg.c
Estimated timestamp from git blame: 2024-02-09
Background
On ChromeOS, when the feature kLanguagePacksFonts is enabled, LanguagePackFontService manages localized font assets (e.g., Japanese and Korean). When a localized font pack DLC finishes mounting or downloading, the service registers the font directory on the browser process UI thread:
- Writer Path (UI Thread):
LanguagePackFontService::AddFontDirFromPackResult(atchrome/browser/ash/language_packs/language_pack_font_service.cc:115) ->gfx::AddAppFontDir->FcConfigAppFontAddDir(atui/gfx/linux/fontconfig_util.cc:124/third_party/fontconfig/src/src/fccfg.c:2760).
Concurrently, a sandboxed renderer process can communicate with the background font service to resolve localized characters. The font service handles these Mojo IPCs on a background ThreadPool sequenced task runner:
- Reader Path (ThreadPool Thread):
FontServiceApp::FallbackFontForCharacter->gfx::GetFallbackFontForChar->CachedFontSet::CreateForLocale->FcFontSort(atui/gfx/font_fallback_linux.cc:474).
The Vulnerability
Historically, Skia wrapped FontConfig calls in FCLocker to enforce serialization. However, for FontConfig versions 2.13.93 or newer (the bundled version in Chrome is 2.17.x), FCLocker behaves as a no-op because it assumes FontConfig’s internal synchronization is sufficient:
// third_party/skia/src/ports/SkFontConfigInterface_direct.cpp
struct FCLocker {
inline static constexpr int FontConfigThreadSafeVersion = 21393;
FCLocker() {
if (FcGetVersion() < FontConfigThreadSafeVersion) {
f_c_mutex().acquire();
}
}
...
};
While FontConfig’s lock_config() protects pointer modifications to the global _fcConfig pointer itself, it does not protect against concurrent modifications to individual FcFontSet structures, such as config->fonts[FcSetApplication]. This lack of synchronization permits concurrent execution of FcFontSetAdd (on the UI Thread) and FcFontSetSort (on the ThreadPool thread) on the same font set.
There are two primary potential exploitation primitives resulting from this race:
Primitive A: Use-After-Free (UAF)
During font registration, FcFontSetAdd (at third_party/fontconfig/src/src/fcfs.c:58) may trigger a resize of the underlying pointer array when capacity is reached:
f = (FcPattern **)realloc (s->fonts, sfont * sizeof (FcPattern *));
If the heap manager relocates this allocation, the old s->fonts buffer is freed. If the ThreadPool thread is currently executing FcFontSetSort (at third_party/fontconfig/src/src/fcmatch.c:1182-1187), it will read from and dereference the freed pointer array, leading to a Use-After-Free.
Primitive B: Heap Out-Of-Bounds (OOB) Write via TOCTOU
In FcFontSetSort, the ThreadPool thread performs a two-pass operation on the font set. First, it reads s->nfont to determine the required allocation size for sorting nodes:
nnodes = 0;
for (set = 0; set < nsets; set++) {
s = sets[set];
...
nnodes += s->nfont;
}
nodes = malloc (nnodes * sizeof (FcSortNode) + ...);
Subsequently, it iterates through the loop again to populate the buffer:
for (f = 0; f < s->nfont; f++) {
newp->pattern = s->fonts[f];
*nodep = newp;
newp++;
nodep++;
}
If the UI thread inserts a font via FcFontSetAdd between these two passes, s->nfont is incremented. The second loop then runs for more iterations than originally allocated, resulting in an out-of-bounds heap write of up to ~256 bytes per added font directly into adjacent heap allocations.
Potential Attack Scenario
Note: These are potential steps estimated from code analysis; our tooling does not currently have the capability to execute live exploit code.
- An attacker compromises the sandboxed renderer process (e.g., via a separate V8 or Blink vulnerability).
- The compromised renderer connects to the
font_service::mojom::FontServiceinterface. - The renderer floods
FallbackFontForCharacterIPC requests with randomized, unique locale strings (e.g.,"en-US-x-0","en-US-x-1", …) to force cache misses, keeping background threads actively performingFcFontSortqueries. - The renderer triggers a localized font DLC installation or causes the user’s
kPreferredLanguagessetting to update, forcing the UI thread to execute font directory registration viaFcConfigAppFontAddDir. - The race condition occurs, resulting in heap corruption (UAF or Heap OOB Write) in the browser process, which could potentially be leveraged for a sandbox escape.
Suggested Fix
To remediate this issue, FontConfig modifications on the UI thread must be serialized with background font queries. Since Skia’s FCLocker assumes FontConfig is thread-safe, Chrome should enforce its own synchronization lock in the browser process around any operations modifying the application-level font config (FcConfigAppFontAddDir), or ensure that all FontConfig-related queries and modifications are dispatched and executed on a single dedicated sequence runner rather than running concurrently on ThreadPool threads and the UI thread.
Evaluated with Chrome root at commit: d8b226a3be7c9c1ac9240c09e14698866c82e4ac
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
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.