2.1.243 native build segfaults instantly on startup: interposed free() crashes on free(NULL) from glibc 2.44 newlocale

Status Open
Reported on v2.1.241
Maintainer reply None cached
Activity 3 comments · opened Aug 25, 2026

Summary

The native (Bun standalone) Linux x64 build of 2.1.243 segfaults on every invocation — including claude --version — before printing a single byte. 2.1.241, 2.1.236 and 2.1.235 all run fine on the same machine.

This is not a corrupt download: I hashed my copy, deleted it, re-downloaded via claude install latest, and got a byte-identical binary (sha256 4b0dafeedd0b469c41988e200036fd773e7553ba960349c9f02a82c6d1f2ba27) that segfaults identically.

I have a core dump and disassembly; the root cause is below.

Environment

  • CachyOS Linux, kernel 7.2.0-1-cachyos, bare metal
  • glibc 2.44 (ldd (GNU libc) 2.44)
  • Intel Core i7-7700 (sse4_2, avx, avx2 — no AVX-512)
  • Both 2.1.241 and 2.1.243 embed Bun v1.4.0 (per strings), so it is not a runtime bump
  • Locale-independent: crashes under LC_ALL=C, C.UTF-8, and env -i HOME=$HOME PATH=/usr/bin
  • ASLR-independent: crashes under setarch -R (binary is non-PIE anyway)

Crash

dmesg (identical ip on every run):

claude[15182]: segfault at 0 ip 0000000001d10458 sp 00007ffe2cb0dfb0 error 4 in 2.1.243[1b0f458,1953000+3b33000]

error 4 + at 0 = user-mode read of unmapped page at address 0. si_code: SEGV_MAPERR.

coredumpctl backtrace:

#0  0x0000000001d10458 free (2.1.243 + 0x1b10458)
#1  0x00007fdfbce3a1ab __newlocale (libc.so.6 + 0x3a1ab)
#2  0x0000000001abd282 n/a (2.1.243 + 0x18bd282)
#3  0x00007fdfbceb2584 n/a (libc.so.6 + 0xb2584)
#4  0x00007fdfbceb26a9 pthread_once (libc.so.6 + 0xb26a9)
#5  0x0000000001abc2d2 n/a (2.1.243 + 0x18bc2d2)
...
#16 0x00007fdfbce27e23 __libc_start_main (libc.so.6 + 0x27e23)

Registers at the fault (gdb on the core):

rax 0x0   rcx 0x0   rdi 0x0   rsi 0x0

Root cause

2.1.243 newly interposes the C allocator, and the interposed free crashes on free(NULL).

  1. 2.1.243 exports malloc / free / __libc_malloc / __libc_free / __libc_cfree as strong symbols from the executable. 2.1.241 exports none of these — this is the regression between the two builds:

``
$ nm 2.1.241 | grep -E ' (free|malloc|__libc_)' → (nothing)
$ nm 2.1.243 | grep -E ' (free|malloc|__libc_)'
0000000001d10430 T free
0000000001d10430 T __libc_cfree
0000000000d10430 T __libc_free
0000000001d102d0 T __libc_malloc
0000000001d102d0 T malloc
``

Because the executable wins symbol resolution, glibc's own internal free calls now land in the bundled allocator (mimalloc).

  1. During startup, a static initializer in the binary (frame #2, under pthread_once) calls glibc newlocale(). On glibc 2.44 this path calls free(NULL) (legal; must be a no-op).
  1. The interposed free does its segment-map lookup before any NULL check. Disassembly at the fault:

``asm
0000000001d10430 <__libc_cfree>:
1d10434: mov %rdi,%rsi
1d10437: mov 0x3781982(%rip),%rax # segment map @ 0x5491dc0
1d1043e: mov %rdi,%rcx
1d10441: shr $0x1d,%rcx
1d10445: mov 0x80(%rax,%rcx,8),%rax # map entry for ptr 0 → 0
1d1044d: mov %esi,%ecx
1d1044f: shr $0xd,%ecx
1d10452: and $0xfff8,%ecx
1d10458: mov (%rax,%rcx,1),%rdi # ← deref of NULL entry: SIGSEGV
1d1045c: test %rdi,%rdi # null check comes one insn too late
1d1045f: je ...
``

With rdi = 0 (the freed pointer): the segment-map entry for pointer 0 is 0, and the code dereferences it at 1d10458 before the test %rdi,%rdi guard. Deterministic crash at a fixed address on every run — matching the identical ip in all dmesg lines.

So any glibc whose newlocale/startup path calls free(NULL) (glibc 2.44 here) kills 2.1.243 instantly, regardless of locale settings or environment. Upstream mimalloc handles a NULL segment-map entry (mi_free(NULL) is a no-op), so this looks like the check was reordered/dropped in this build's interposition shim or by LTO.

Steps to reproduce

On a glibc 2.44 system (e.g. up-to-date Arch/CachyOS):

~/.local/share/claude/versions/2.1.243 --version   # SIGSEGV
~/.local/share/claude/versions/2.1.241 --version   # 2.1.241 (Claude Code)

Workaround

ln -sf ~/.local/share/claude/versions/2.1.241 ~/.local/bin/claude

plus pinning in ~/.claude/settings.json:

{ "autoUpdatesChannel": "stable", "minimumVersion": "2.1.241" }

(Note: claude install latest repoints the symlink back at the broken build, so the auto-updater re-breaks the install until pinned.)

Possibly related

#84293 reports a startup crash on Bun 1.4.0/x64-baseline, but that one is si_code=SI_KERNEL (general protection fault) and was closed as a corrupt download. Mine is SEGV_MAPERR with a verified-identical re-download — different fault class, and evidence that report may have been misdiagnosed.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗