Skip to content

ghostty: link against system fontconfig to fix crashes - #476

Closed
maxib0n wants to merge 2 commits into
scottames:mainfrom
maxib0n:ghostty-fix-fontconfig-symbol-collision
Closed

maxib0n wants to merge 2 commits 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.contentScaleCallback → setFontSize → SharedGridSet.ref → Fontconfig.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

maxib0n and others added 2 commits July 8, 2026 02:48
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.
@scottames

Copy link
Copy Markdown
Owner

Thanks for this @maxib0n. Unfortunately this is blocked on a Zig packaging conflict. Currently Ghostty requires Zig 0.15.2 (exact) to build. Fedora does not offer this. I'll look into solving this, but it won't be immediate.

@kapuchai

Copy link
Copy Markdown

Hello! I'm confirming this issue as well on ghostty-1.3.1-2.fc44 (from this copr, Fedora 44 Workstation, GNOME/Wayland, system fontconfig 2.17.0-4.fc44).

Got six SIGSEGV core dumps within ~7 hours, all inside fontconfig frames, matching the symbol-interposition problem this PR fixes.
$ nm -D /usr/bin/ghostty | grep -c ' T Fc'
27
$ ldd /usr/bin/ghostty | grep fontconfig
libfontconfig.so.1 => /lib64/libfontconfig.so.1 (...)

The clearest core shows GTK4 itself calling into the bundled copy: an xdg-desktop-portal settings change => gtk_settings_notify (libgtk-4.so.1) => FcInitReinitialize resolved to an address inside the ghostty executable (the interposed static 2.14.x copy) => FcConfigSetCurrent => FcConfigDestroy => GP fault destroying an FcPattern that points just past the end of a le64.cache-9 mapping created by system fontconfig 2.17. Four more cores are renderer-thread crashes in FcCompare (fcmatch.c:622) during glyph-fallback discovery: three SEGV_MAPERR with si_addr equal to the candidate FcPattern* (in the one of these with a captured mapping excerpt, that address sits in an unmapped hole directly after a cache-9 mapping), the fourth a GP fault on a non-canonical address derived from the same pattern pointer. One more core is a GP fault in free() under FcStrSetDestroy. Both cores where I captured process mappings (the GTK main-thread crash and one FcCompare crash) have the same font directories mmap'd twice — cache-8 (bundled 2.14.x) and cache-9 (system 2.17.0) side by side.

Upstream context: this is ghostty-org/ghostty#10432 / discussions #10568 and #12555; the upstream fix (ghostty-org/ghostty#11152, system fontconfig by default on Linux) was merged 2026-03-03 and reverted the next day, so a spec-level -fsys=fontconfig like this PR is currently the only clean fix on Fedora. Would be great to get this merged and rebuilt. I have full anonymized backtraces for all six cores if useful.
ghostty-fontconfig-evidence.zip

@kapuchai

kapuchai commented Aug 8, 2026

Copy link
Copy Markdown

Any updates on this?

@scottames

Copy link
Copy Markdown
Owner

Any updates on this?

Now that ghostty-org/ghostty#12228 is in the next Ghostty release should resolve this.

@jerem

jerem commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

I hit this independently on Fedora 44 and filed #548 / #549 before spotting this PR — my mistake, #549 is closed as a duplicate of this one. My coredumps match what @maxib0n and @kapuchai describe exactly (GTK4 gtk_settings_notify → FcInitReinitialize resolved into the bundled copy, plus renderer-thread faults in FcCompare at fcmatch.c:622 during fallback discovery, with the faulting FcPattern* sitting in an unmapped one-page hole adjacent to a cache-9 mapping, and cache-8 + cache-9 mapped side by side in the same process).

Two things I found that this PR doesn't currently cover, in case they're useful when it unblocks.

fontconfig isn't the only vendored font library colliding. 1.3.1-2.fc44 also exports freetype and harfbuzz:

$ for p in Fc FT_ hb_; do echo "$p: $(nm -D --defined-only /usr/bin/ghostty | grep -cE " [TWD] $p")"; done
Fc: 27
FT_: 57
hb_: 518

All three are simultaneously linked from /lib64 via GTK4, so -fsys=fontconfig alone leaves 575 interposed symbols across the other two. I don't have a crash pinned to freetype or harfbuzz specifically — fontconfig is clearly the one that bites, because FcInitReinitialize gives GTK a way to destroy state under a live consumer — but the same ODR hazard is there. Adding all three zeroes it out:

$ for p in Fc FT_ hb_; do echo "$p: $(nm -D --defined-only /usr/bin/ghostty | grep -cE " [TWD] $p")"; done
Fc: 0
FT_: 0
hb_: 0

-fsys=freetype additionally needs BuildRequires: bzip2-devel, because SharedDeps.zig:152 calls linkSystemLibrary2("bzip2") on that path and it wants bzip2.pc.

On the Zig blocker — confirming @scottames is right that it's exact, for anyone who assumed minimum_zig_version meant a floor. src/build/zig.zig:9-11:

if (current_vsn.major != required_vsn.major or
    current_vsn.minor != required_vsn.minor or
    current_vsn.patch < required_vsn.patch)

Major and minor must match exactly, patch is a floor — so 0.15.2 or 0.15.3+, never 0.16.x. Fedora 44 now ships zig 0.16.0, which means BuildRequires: zig is currently unsatisfiable for this spec regardless of this PR. I did build 1.3.1 with all three -fsys= flags successfully against an upstream zig-x86_64-linux-0.15.2 tarball on PATH (209/209 steps), and have been running it since — so the spec change itself is sound once there's a 0.15.2 toolchain in CI.

Happy to push the three-flag version as a commit here or as a separate PR if that's helpful, or to leave it alone if the plan is to wait for ghostty-org/ghostty#12228 in the next release.

One question, since I couldn't find the reasoning: ghostty-org/ghostty#11152 made system fontconfig the Linux default and was reverted a day later. Does anyone know what broke? Worth knowing before a spec-level -fsys= ships to everyone, in case it reintroduces whatever caused that revert.

@scottames

Copy link
Copy Markdown
Owner

Thanks for the extra effort @jerem! I think it's worth including the extra flags as well. Let's re-open your PR and go with it once #550 is merged and a new zig package (zig015) is built in this copr.

@scottames

Copy link
Copy Markdown
Owner

Closing in favor of #549

@scottames scottames closed this Aug 12, 2026
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.

4 participants