Skip to content

ghostty: link against system fontconfig to fix crashes#476

Open
maxib0n wants to merge 1 commit into
scottames:mainfrom
maxib0n:ghostty-fix-fontconfig-symbol-collision
Open

ghostty: link against system fontconfig to fix crashes#476
maxib0n wants to merge 1 commit into
scottames:mainfrom
maxib0n:ghostty-fix-fontconfig-symbol-collision

Conversation

@maxib0n

@maxib0n maxib0n commented Jul 8, 2026

Copy link
Copy Markdown

Summary

Ghostty statically bundles its own copy of fontconfig. Because the resulting binary is a PIE executable built without hiding those symbols, it globally exports ~27 Fc* functions. GTK4/Pango load the system libfontconfig.so.1 into the same process, and due to ELF symbol interposition, calls made from inside libfontconfig.so.1 (and other GTK-side consumers, e.g. glycin's SVG loader, see ghostty-org/ghostty#12555) get redirected to Ghostty's bundled copy instead, corrupting state shared between the two fontconfig instances.

This causes reproducible SIGSEGV/SIGABRT crashes (invalid free in FcPatternDestroy/FcFontSetDestroy, or segfaults in FcFontSort). The easiest reliable trigger is moving a Ghostty window between two monitors with different scale factors, which forces a font grid rebuild through fontconfig (Surface.contentScaleCallbacksetFontSizeSharedGridSet.refFontconfig.discover).

Passing -fsys=fontconfig to zig build makes Ghostty dynamically link the system fontconfig instead of statically bundling its own, so there's a single fontconfig instance in the process.

Verification

On the currently published ghostty-1.3.1-2.fc44 build:

$ nm -D /usr/bin/ghostty | grep -c '^[0-9a-f]* T Fc'
27
$ readelf -d /usr/bin/ghostty | grep -i needed | grep -i font
(nothing — fontconfig is not a declared dependency of the binary itself)

8 coredumps collected over the past week, 6 with matching stack traces through contentScaleCallback → setFontSize → SharedGridSet.ref → FcFontSetDestroy/FcPatternDestroy or FcFontSort.

Rebuilt locally with -fsys=fontconfig added (same Zig 0.15.2, same build flags otherwise):

$ nm -D ghostty | grep -c '^[0-9a-f]* T Fc'
0
$ readelf -d ghostty | grep -i needed | grep -i font
 0x0000000000000001 (NEEDED)  Shared library: [libfontconfig.so.1]

Ran the patched binary and repeatedly moved the window between the two monitors that reliably crashed the unpatched build (scale 1.25 vs scale 1) — no crash.

Test plan

  • zig build succeeds with -fsys=fontconfig added (209/209 steps)
  • Confirmed 0 exported Fc* symbols vs 27 before
  • Confirmed libfontconfig.so.1 is a proper NEEDED entry
  • Manually reproduced the crash on the unpatched binary, then confirmed the patched binary survives the same repro steps

Ghostty statically bundles its own copy of fontconfig, and because the
resulting binary is a PIE executable built without hiding those
symbols, it globally exports ~27 Fc* functions. GTK4/Pango load the
system libfontconfig.so.1 into the same process. Due to ELF symbol
interposition, calls made from inside libfontconfig.so.1 (and other
GTK-side consumers, e.g. glycin's SVG loader per ghostty-org/ghostty
discussion #12555) get redirected to Ghostty's bundled copy instead,
corrupting state shared between the two fontconfig instances.

This causes reproducible SIGSEGV/SIGABRT crashes (double free / invalid
free in FcPatternDestroy, FcFontSetDestroy, or segfaults in
FcFontSort), most easily triggered by moving a Ghostty window between
monitors with different scale factors, which forces a font grid
rebuild through fontconfig.

Passing -fsys=fontconfig makes Ghostty dynamically link the system
fontconfig instead of statically bundling its own, leaving a single
fontconfig instance in the process. Verified locally: the resulting
binary exports zero Fc* symbols (down from 27), lists
libfontconfig.so.1 as a proper NEEDED entry, and no longer crashes
under repeated monitor moves that reliably crashed the unpatched
build.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant