Segfault at startup on Linux since 2.1.242 — bundled allocator's free null-derefs when called from glibc __newlocale
Segfault at startup on Linux since 2.1.242 — bundled allocator's free null-derefs when called from glibc __newlocale
Summary
Starting with 2.1.242, the native Linux binary segfaults immediately on launch. claude --version is enough to reproduce — the crash happens during early runtime initialization, before main.
The backtrace shows glibc's __newlocale calling free(), which resolves via symbol interposition to the allocator bundled inside the Claude Code binary. That free performs a two-level page-map lookup, gets a NULL first-level entry, and dereferences it without a guard.
2.1.241 and earlier launch normally on the same machine with no system changes in between.
Environment
| | |
|---|---|
| OS | Arch Linux (rolling) |
| Kernel | 7.1.8-arch1-3 |
| Arch | x86_64 |
| glibc | 2.44 (GNU libc — not musl) |
| Install method | native installer (~/.local/share/claude/versions/) |
| First bad version | 2.1.242 |
| Last known good | 2.1.241 |
Reproduction
$ claude --version
Segmentation fault (core dumped) claude --version
$ ~/.local/share/claude/versions/2.1.242
Segmentation fault (core dumped)
100% reproducible, on every invocation, at the same instruction pointer. Reproduces when the version binary is executed directly, so the launcher symlink is not involved.
Backtrace
signal: 11 (SEGV), si_code: SEGV_MAPERR, Program terminated with signal SIGSEGV at #0 ... in free ().
#0 0x0000000001d10458 free (2.1.243 + 0x1b10458)
#1 0x00007f6f1f03530a __newlocale (libc.so.6 + 0x3530a)
#2 0x0000000001abd282 n/a (2.1.243 + 0x18bd282)
#3 0x00007f6f1f09d7fc n/a (libc.so.6 + 0x9d7fc)
#4 0x00007f6f1f09d879 pthread_once (libc.so.6 + 0x9d879)
#5 0x0000000001abc2d2 n/a (2.1.243 + 0x18bc2d2)
#6 0x0000000001ace6cf n/a (2.1.243 + 0x18ce6cf)
#7 0x0000000001abdc0a n/a (2.1.243 + 0x18bdc0a)
#8 0x0000000001abe9c4 n/a (2.1.243 + 0x18be9c4)
#9 0x00007f6f1f09d7fc n/a (libc.so.6 + 0x9d7fc)
#10 0x00007f6f1f09d879 pthread_once (libc.so.6 + 0x9d879)
#11 0x0000000001abea15 n/a (2.1.243 + 0x18bea15)
#12 0x0000000001abead7 n/a (2.1.243 + 0x18bead7)
#13 0x0000000001abb922 n/a (2.1.243 + 0x18bb922)
#14 0x0000000001abb724 n/a (2.1.243 + 0x18bb724)
#15 0x0000000001aadadd n/a (2.1.243 + 0x18adadd)
#16 0x00007f6f1f027892 __libc_start_main (libc.so.6 + 0x27892)
#17 0x0000000001953a2e n/a (2.1.243 + 0x1753a2e)
(Frames captured from the 2.1.243 core; 2.1.242 fails identically.)
Kernel fault record
claude[30104]: segfault at 0 ip 0000000001d10458 sp 00007ffe66331100 error 4 \
in 2.1.243[1b0f458,1953000+3b33000] likely on CPU 0 (core 0, socket 0)
Code: cc cc 55 48 89 e5 48 89 fe 48 8b 05 82 19 78 03 48 89 f9 48 c1 e9 1d \
48 8b 84 c8 80 00 00 00 89 f1 c1 e9 0d 81 e1 f8 ff 00 00 <48> 8b 3c 08 \
48 85 ff 74 1e 48 8b 07 64 48 33 04 25 00 00 00 00 75
segfault at 0 with error 4 — a user-mode read of address 0. Identical ip across invocations with differing stack pointers, so this is deterministic rather than corruption or a race.
Analysis
The faulting instruction sequence in free decodes to a two-level table lookup:
mov rax, [rip+0x3781982] ; page-map base
shr rcx, 0x1d ; index by addr >> 29 (512 MB granularity)
mov rax, [rax+rcx*8+0x80] ; first-level entry -> NULL
mov ecx, esi
shr ecx, 0xd ; index by addr >> 13 (8 KB granularity)
and ecx, 0xfff8
mov rdi, [rax+rcx] ; <-- faults, rax == 0
A 512 MB first level indexing 8 KB second-level entries matches the page-map layout used by mimalloc-style allocators.
Putting the backtrace and the disassembly together:
- The binary bundles its own allocator and exports
malloc/free, interposing glibc's. - During early startup — inside a
pthread_once-guarded initializer reached from__libc_start_main, beforemain— glibc's__newlocalefrees a buffer. - Symbol interposition routes that
freeinto the bundled allocator. - The allocator's page map either isn't initialized yet at that point in startup, or the pointer was allocated by glibc's
mallocbefore interposition took effect and therefore isn't in the map. - Either way the first-level lookup returns NULL and is dereferenced without a guard → SIGSEGV.
This is the classic allocator-interposition failure mode: a foreign or not-yet-tracked pointer reaching an interposed free during static initialization.
Why it appeared in 2.1.242. Something in that release changed the bundled allocator version or the static-init ordering. Nothing else on this machine changed — /var/log/pacman.log shows no transactions on the day of the regression, so no system library or kernel update is a confounder. The only change was the background auto-updater.
Possible glibc-version dependence. __newlocale's allocation and free behavior during early init has changed across glibc releases. glibc 2.44 may be freeing a buffer at a point where older glibc did not, which would explain why this is not showing up universally. Worth testing against glibc 2.44 specifically, since rolling-release distributions will hit it first and other users will follow as 2.44 propagates.
Suggested fix direction. Guard the page-map lookup against a NULL first-level entry and forward unrecognized pointers to the system free, which is the standard fallback for interposing allocators; or ensure the page map is fully initialized before any interposed free can be reached from libc static init.
Version bisect
| Version | Result |
|---|---|
| 2.1.239 | launches |
| 2.1.240 | launches |
| 2.1.241 | launches |
| 2.1.242 | segfault |
| 2.1.243 | segfault |
All tested on the same machine in the same session, same glibc and kernel. Regression introduced in 2.1.242.
Ruled out
- musl/glibc binary mismatch —
ldd --versionreports GNU libc 2.44; no/lib/libc.musl*present. - Missing shared libraries —
ldd "$(command -v claude)" | grep "not found"returns nothing. - Architecture mismatch —
uname -misx86_64. - Missing CPU instruction set — a missing AVX-class instruction raises
SIGILL, notSIGSEGV. - Environment contamination — reproduces under
env -i, so noLD_PRELOAD,NODE_OPTIONS, or similar. - ASLR / address-space layout — reproduces under
setarch -R.vm.mmap_rnd_bits = 28,kernel.randomize_va_space = 2, both stock. - User config or credentials — reproduces on
--version, which faults before config load. - Launcher/symlink state — reproduces when the version binary is executed directly.
Workaround
Roll back to 2.1.241. Since the broken binary is what ~/.local/bin/claude points at, install has to be invoked through a working binary:
~/.local/share/claude/versions/2.1.241 install 2.1.241
Setting "autoUpdatesChannel": "stable" avoids being auto-updated back into the regression.
Note on the supported matrix
Arch is outside the documented support floor (Ubuntu 20.04+, Debian 10+, Alpine 3.19+), and glibc 2.44 is newer than the binary is likely built against. That is worth stating plainly. It does not explain the regression on its own, though: the same glibc and kernel run 2.1.241 without issue, so 2.1.242 introduced the fault rather than merely exposing a pre-existing incompatibility. And an unguarded free on the libc static-init path is a latent bug regardless of which glibc first triggers it.
---
Diagnosed and drafted by Claude (Opus 5); reproduction, core dump, system data, and version bisect performed and verified by @Solteris-Dev, who reviewed and submitted this report.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗