Segfault at startup on Linux since 2.1.242 — bundled allocator's free null-derefs when called from glibc __newlocale

Status Open
Reported on v2.1.241
Maintainer reply None cached
Activity 1 comment · opened Aug 26, 2026

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:

  1. The binary bundles its own allocator and exports malloc/free, interposing glibc's.
  2. During early startup — inside a pthread_once-guarded initializer reached from __libc_start_main, before main — glibc's __newlocale frees a buffer.
  3. Symbol interposition routes that free into the bundled allocator.
  4. The allocator's page map either isn't initialized yet at that point in startup, or the pointer was allocated by glibc's malloc before interposition took effect and therefore isn't in the map.
  5. 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 mismatchldd --version reports GNU libc 2.44; no /lib/libc.musl* present.
  • Missing shared librariesldd "$(command -v claude)" | grep "not found" returns nothing.
  • Architecture mismatchuname -m is x86_64.
  • Missing CPU instruction set — a missing AVX-class instruction raises SIGILL, not SIGSEGV.
  • Environment contamination — reproduces under env -i, so no LD_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.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗