CVE-2026-106426
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
BindOnceui/gfx/linux/fontconfig_util.cc |
modified |
Files Changed
ui/gfx/font_util.ccui/gfx/linux/fontconfig_util.ccui/gfx/linux/fontconfig_util.h
Patch
From 52a66b712fe87f445a917357626ed9447d63f686 Mon Sep 17 00:00:00 2001 From: Anthony Vallee-Dubois <[email protected]> Date: Fri, 04 Sep 2026 12:53:28 -0700 Subject: [PATCH] Make fontconfig global config creation synchronous Async initialization was introduced in crrev.com/1255274 as a performance improvement. Although fontconfig is generally thread-safe, it is unsafe to use the library in the middle of initialization. See the details and PoC in Issue 547065823. This CL reverts fontconfig init to being synchronous to avoid this race. Bug: 41496758,547065823 Change-Id: Ie5563115f9e453305ffbe98bd7ae6c99ebdebad3 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8310116 Reviewed-by: Etienne Bergeron <[email protected]> Commit-Queue: Anthony Vallee-Dubois <[email protected]> Reviewed-by: Thomas Anderson <[email protected]> Cr-Commit-Position: refs/heads/main@{#1692682} --- diff --git a/ui/gfx/font_util.cc b/ui/gfx/font_util.cc index 5c43898..6e0e298 100644 --- a/ui/gfx/font_util.cc +++ b/ui/gfx/font_util.cc @@ -26,8 +26,11 @@ // the long delay the user would have seen on first rendering. #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) - // Early initialize FontConfig. - InitializeGlobalFontConfigAsync(); + // Ensures the config is created on this thread. It's generally safe to send + // concurrent match requests to fontconfig, but it's unsafe to send match + // requests concurrently to fontconfig initialization. + FcConfig* config = GetGlobalFontConfig(); + DCHECK(config); #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) #if BUILDFLAG(IS_WIN) diff --git a/ui/gfx/linux/fontconfig_util.cc b/ui/gfx/linux/fontconfig_util.cc index cb926c9b..a1aecb9 100644 --- a/ui/gfx/linux/fontconfig_util.cc +++ b/ui/gfx/linux/fontconfig_util.cc @@ -10,8 +10,6 @@ #include "base/environment.h" #include "base/memory/raw_ptr.h" #include "base/no_destructor.h" -#include "base/task/thread_pool.h" -#include "base/task/thread_pool/thread_pool_instance.h" #include "ui/gfx/font_render_params.h" #if BUILDFLAG(IS_CHROMEOS) @@ -207,18 +205,6 @@ } // namespace -void InitializeGlobalFontConfigAsync() { - if (base::ThreadPoolInstance::Get()) { - base::ThreadPool::PostTask( - FROM_HERE, - {base::MayBlock(), base::TaskPriority::USER_BLOCKING, - base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, - base::BindOnce([]() { GlobalFontConfig::GetInstance(); })); - } else { - GlobalFontConfig::GetInstance(); - } -} - FcConfig* GetGlobalFontConfig() { return GlobalFontConfig::GetInstance()->Get(); } diff --git a/ui/gfx/linux/fontconfig_util.h b/ui/gfx/linux/fontconfig_util.h index 616c717..83381db0 100644 --- a/ui/gfx/linux/fontconfig_util.h +++ b/ui/gfx/linux/fontconfig_util.h @@ -18,13 +18,6 @@ }; using ScopedFcPattern = std::unique_ptr<FcPattern, FcPatternDeleter>; -// Initializes FontConfig on a worker thread if a thread pool instance is -// available, otherwise initializes FontConfig in a blocking fashion on the -// calling thread. If this function is not called, the first call to -// GetGlobalFontConfig() will implicitly initialize FontConfig. Can be called -// on any thread. -COMPONENT_EXPORT(GFX) void InitializeGlobalFontConfigAsync(); - // Retrieve the global font config. Must be called on the main thread. COMPONENT_EXPORT(GFX) FcConfig* GetGlobalFontConfig(); COMPONENT_EXPORT(GFX) void OverrideGlobalFontConfigForTesting(FcConfig* config);
Original Bug Report
font_data_service MatchFamilyName: corrupt FC_FAMILY pointer causes wild read in browser process
Steps to reproduce the problem
NO RELIABLE REPRODUCER. Please read this section before triaging - I can describe the conditions precisely but cannot trigger the crash on demand.
Observed frequency: 2 occurrences in ~6 weeks, across several thousand browser sessions in a CI suite that runs continuously (~38 min per build, ~24 builds/day). Observed twice (2026-07-08, 2026-08-17); only the second has a core dump. Both showed the same external signature — browser process death on a freshly created session during its first page load — but only the second is confirmed to be this crash.
Environment:
- Image: selenium/standalone-chromium (floating tag), Debian-based
- chromium 151.0.7922.108-2 (Debian Chromium Team package)
- libfontconfig1 2.15.0-1.1ubuntu2 (SYSTEM fontconfig, not bundled)
- Headless: –headless=new –no-sandbox –disable-gpu –disable-dev-shm-usage –ozone-platform=headless –use-angle=swiftshader-webgl –test-type=webdriver
- Driven by Selenium 4.46.0 / chromedriver 151.0.7922.108
- 3 concurrent chromium processes per container, same user, sharing /home/seluser/.cache/fontconfig and /var/cache/fontconfig
What the page was doing: a normal HTTPS page load; the renderer requested font family “Sans” at size 18, which produced the FontDataService MatchFamilyName IPC on the stack below.
What I have instead of a reproducer: a full core dump of the crashed browser process, symbolized against build-id-matched binaries for both chromium (6a0d4a0b2fd44278635086c919f16cb63827bd44) and libfontconfig (9ea8985291d6ca9e904a796a5767d191039d813d). Every value quoted in the description was read directly out of that core. I can answer follow-up questions against it, or provide it through a private channel - see the note at the end of the description.
Problem Description
Note: this bug report was written with the help of Claude Code
SIGSEGV (si_code=1, SEGV_MAPERR) at 0x488803e223c6 in the BROWSER process, on a ThreadPool thread servicing a FontDataService MatchFamilyName Mojo IPC from a renderer.
NOTE ON BUILD: this is Debian’s chromium package linked against SYSTEM fontconfig 2.15.0. Official Chrome bundles fontconfig, so the final faulting step may differ there. I am filing it here rather than with Debian or fontconfig because, as shown below, the corrupted data originates in an FcPattern that Chromium code built and owns; fontconfig only converts an already-bad pointer into a wild one.
STACK (all frames build-id verified): #0 FcStrCaseWalkerNext fcstr.c:220 #1 FcStrHashIgnoreCase fcstr.c:318 #2 FcHashTableFind (key=0x488803e223c6) fchash.c:105 #3 FamilyTableAdd fccfg.c:1600 #4 FamilyTableInit (p=0x244403955440) fccfg.c:1666 #5 FcConfigSubstituteWithPat (kind=FcMatchPattern) fccfg.c:2072 #6 SkFontConfigInterfaceDirect::matchFamilyName(char const*, SkFontStyle, …) #7 SkFontMgr_FCI::onMatchFamilyStyle(char const*, SkFontStyle const&) #8 font_data_service::FontDataServiceImpl::MatchFamilyName( std::string const&, TypefaceStyle, callback) #9-10 FontDataService Mojo stub dispatch #11-21 mojo InterfaceEndpointClient -> MultiplexRouter -> Connector #22-24 base::ThreadPool TaskTracker
THE CORRUPT OBJECT. The FcPattern at 0x244403955440 (num=8, ref=1) is the one built in SkFontConfigInterfaceDirect::matchFamilyName. Read out of the core, its eight values are:
FAMILY u.s = 0x2444003d2fff <– CORRUPT SLANT 0 WEIGHT 80 WIDTH 100 SCALABLE true LANG “en” (0x2444036e16e0) PRGNAME “chromium” (0x2444036e0590) FONT_WRAPPER “SFNT” (0x2444036e2720)
Exactly one field is bad. The three other string pointers - added by the same function, in the same call - are even-aligned, valid, and clustered together at 0x2444036e_xxxx. The FAMILY pointer is odd-aligned, sits in a completely different part of the heap, and points into a region that is zero-filled for at least 64 bytes either side.
HOW A BAD POINTER BECOMES A WILD READ. fontconfig reserves the low bit of FcValue.u.s to distinguish a real pointer from a serialized-cache offset (fcint.h):
#define FcIsEncodedOffset(p) ((((intptr_t)(p)) & 1) != 0) #define FcOffsetDecode(p) (((intptr_t)(p)) & ~1) #define FcOffsetToPtr(b,o,t) ((t *)((intptr_t)(b) + (ptrdiff_t)(o))) #define FcValueString(v) FcPointerMember(v, u.s, FcChar8)
Because 0x2444003d2fff is odd, FcValueString did not use it as a pointer. It decoded it as an offset and computed:
&value = 0x244403a4f3c8 FcOffsetDecode = 0x2444003d2ffe sum = 0x488803e223c6 == the faulting address, exactly
So a corrupt pointer here yields a read at an essentially arbitrary address, not a contained crash. Had the pointer merely been bogus-but-even, fontconfig would have read the zeros as an empty family name and continued.
WHY THIS LOOKS LIKE A CHROMIUM-SIDE BUG. matchFamilyName populates FC_FAMILY via FcPatternAddString, which copies the string through FcStrdup - so a correctly functioning build always stores fontconfig’s own, even-aligned copy in that field. For it to hold an odd pointer into freed/zeroed memory, something on the Chromium side wrote it after the copy was made. I cannot tell from a single core whether that is a use-after-free, an out-of-bounds write, or an uninitialized read, and I make no claim about exploitability.
CLASSIFICATION NOTES (per the security FAQ):
- Not a null-pointer dereference. The fault address is ~79 TB and is computed from corrupt data, not a small fixed offset.
- Not a stack overflow.
- MiraclePtr cannot apply: the corrupted field lives inside a fontconfig heap object (FcValueList.value.u.s), not a Chromium raw_ptr, so it gets no BackupRefPtr protection.
CONCURRENCY: I examined all 38 threads in the core. No other thread is anywhere inside fontconfig, Skia font code, or FontDataService. One thread was inside partition_alloc::PartitionRoot::FreeInUnknownRoot at the moment of the crash, which is suggestive of allocator churn but proves nothing.
ARTIFACTS: I have the core dump and can answer further questions against it. I have deliberately NOT attached it: it is a ~900 MB memory image of a browser that was logged into our internal test application, so it only contains session cookies, credentials and page content from that environment, nothing important. Happy to share it, or targeted extracts from it, through whatever channel you prefer.
Additional Comments
-
The affected binary is still shipping. selenium/standalone-chromium is a floating tag, and as of today it still resolves to chromium 151.0.7922.108-2 with build-id 6a0d4a0b2fd44278635086c919f16cb63827bd44 - byte-identical to the binary that crashed. So this is not a build that has already rotated out.
-
Impact in our environment: the browser process dying takes the WebDriver session with it, which fails the remainder of the test class. Low impact for us and easily retried; I am reporting it because of what the corrupt pointer implies, not because of the test failure.
-
On the fontconfig side: even if the Chromium-side corruption is fixed, you may want to consider whether the low-bit offset tag in FcValueString is worth defending against in the bundled copy. As it stands, ANY corrupt odd-valued u.s becomes a read at &value + (ptr & ~1) - an arbitrary-offset read rather than a contained fault. I have not filed anything with fontconfig upstream; happy to if you would rather that be tracked there.
-
Provenance caveat, stated so it is not overread: we have seen this external symptom twice (2026-07-08 and 2026-08-17). Only the 2026-08-17 occurrence has a core dump and the analysis above. The July occurrence showed an identical externally visible signature - browser process death on a freshly created session during its first page load - but no core was captured, so I can only say it is consistent with the same bug, not that it is proven to be.
-
I can run further queries against the core on request (heap neighbours of the corrupt allocation, other threads’ stacks, PartitionAlloc state, anything else that would help). We cannot reproduce on demand, so an instrumented build is unlikely to help us catch it, but if you have a specific hypothesis I can test against the existing core, I will.
-
No disclosure deadline or reward expectation from our side.
Summary
font_data_service MatchFamilyName: corrupt FC_FAMILY pointer causes wild read in browser process
Custom Questions
Type of crash:
Browser process crash
Crash state:
CRASH STATE (top frames): FcStrCaseWalkerNext FcStrHashIgnoreCase FcHashTableFind FamilyTableAdd
EXCEPTION RECORD (Linux siginfo, from NT_SIGINFO in the core): si_signo = 11 (SIGSEGV) si_errno = 0 si_code = 1 (SEGV_MAPERR - address not mapped to object) fault address = 0x488803e223c6 crashing thread = LWP 1386 (process pid 1356; host pid 3364063)
FAULTING INSTRUCTION: => 0x7f9ec822a8f8 <FcStrHashIgnoreCase+88>: movzbl (%rcx),%eax 0x7f9ec822a8fb <FcStrHashIgnoreCase+91>: mov %eax,%ecx 0x7f9ec822a8fd <FcStrHashIgnoreCase+93>: not %ecx 0x7f9ec822a8ff <FcStrHashIgnoreCase+95>: and $0xc0,%ecx
i.e. the first byte read of the “string”, through rcx.
REGISTERS: rax 0x0 rbx 0x0 rcx 0x488803e223c6 rdx 0x488803e223c7 rsi 0x488803e223c6 rdi 0x488803e223c6 rbp 0x7f9ec1d91d70 rsp 0x7f9ec1d91d40 r8 0x244402271560 r9 0xfffffffc00000000 r10 0x244403955440 r11 0x7f9ec1d91ec0 r12 0x2444022ecf00 r13 0x488803e223c6 r14 0x7f9ec1d91db0 r15 0x2444022da050 rip 0x7f9ec822a8f8 <FcStrHashIgnoreCase+88> eflags 0x10246 [ PF ZF IF RF ] cs 0x33 ss 0x2b ds 0x0 es 0x0 fs 0x0 gs 0x0 fs_base 0x7f9ec1d946c0 gs_base 0x0
Corroborating: r10 holds the FcPattern (0x244403955440) and r12 the FcHashTable (0x2444022ecf00) named in the frames below.
STACK WITH SYMBOLS (build-id verified against both binaries: chromium 6a0d4a0b2fd44278635086c919f16cb63827bd44, libfontconfig 9ea8985291d6ca9e904a796a5767d191039d813d):
#0 FcStrCaseWalkerNext (w=0x7f9ec1d91d40) fontconfig-2.15.0/src/fcstr.c:220 #1 FcStrHashIgnoreCase (s=<optimized out>) fontconfig-2.15.0/src/fcstr.c:318 #2 FcHashTableFind (table=0x2444022ecf00, key=0x488803e223c6, value=0x7f9ec1d91db0) fontconfig-2.15.0/src/fchash.c:105 #3 FamilyTableAdd (table=0x7f9ec1d91eb0, values=<optimized out>) fontconfig-2.15.0/src/fccfg.c:1600 #4 FamilyTableInit (p=0x244403955440, table=0x7f9ec1d91eb0) fontconfig-2.15.0/src/fccfg.c:1666 #5 IA__FcConfigSubstituteWithPat (config=<optimized out>, p=0x244403955440, p_pat=0x0, kind=FcMatchPattern) fontconfig-2.15.0/src/fccfg.c:2072 #6 SkFontConfigInterfaceDirect::matchFamilyName(char const*, SkFontStyle, SkFontConfigInterface::FontIdentity*, SkString*, SkFontStyle*) #7 SkFontMgr_FCI::onMatchFamilyStyle(char const*, SkFontStyle const&) const #8 font_data_service::FontDataServiceImpl::MatchFamilyName( std::string const&, mojo::InlinedStructPtr<font_data_service::mojom::TypefaceStyle>, base::OnceCallback<void (mojo::StructPtr< font_data_service::mojom::MatchFamilyNameResult>)>) #9 font_data_service::mojom::FontDataServiceStubDispatch:: AcceptWithResponder(…) #10 font_data_service::mojom::FontDataServiceStub< mojo::RawPtrImplRefTraits<…>>::AcceptWithResponder(…) #11 mojo::InterfaceEndpointClient::HandleIncomingMessageThunk::Accept #12 mojo::MessageDispatcher::Accept #13 mojo::InterfaceEndpointClient::HandleIncomingMessage #14 mojo::internal::MultiplexRouter::ProcessIncomingMessage #15 mojo::internal::MultiplexRouter::Accept #16 mojo::MessageDispatcher::Accept #17 mojo::Connector::DispatchMessage #18 mojo::Connector::ReadAllAvailableMessages #19 mojo::Connector::OnWatcherHandleReady #20 base::RepeatingCallback<void ()>::Run() const & #21 mojo::SimpleWatcher::OnHandleReady #22 base::TaskAnnotator::RunTaskImpl #23 base::internal::TaskTracker::RunSkipOnShutdown #24 base::internal::TaskTracker::RunTask
Reporter credit:
Emond Papegaaij
Additional Data
Category: Security
Chrome Channel: Not sure
Regression: N/A \