Native linux-x64 binary segfaults on Arch Linux (glibc 2.44) — free() called from libc newlocale() crashes with NULL table pointer

Status Open
Maintainer reply None cached
Activity 1 comment · opened Aug 25, 2026

Description

The native (Bun-compiled) claude binary segfaults on Arch Linux (rolling release) due to an allocator‑interposition conflict with a newer glibc runtime. The crash is deterministic (same crash address every run) and reproducible with both the install.sh bootstrapper and the pre‑downloaded binary run directly.

Root cause: the binary statically exports its own malloc/free symbols, which (via normal ELF symbol resolution) end up intercepting free() calls made internally by the system libc.so.6 (e.g. from newlocale() during process/locale bootstrap). On this glibc version the pointer being freed was allocated by the system libc's own allocator, not the binary's bundled one, so the bundled free() dereferences an uninitialized/mismatched heap-metadata table pointer (RAX == 0) and crashes.

I confirmed the exact same binary works fine under an older glibc, and that switching to the musl build avoids the bug entirely (see "Workaround" below).

Environment

  • OS: Arch Linux (rolling release), running under WSL2
  • Kernel: 6.18.33.2-microsoft-standard-WSL2
  • glibc: 2.44+r24+g16be1518495f-1 (Arch's current rolling package)
  • Claude Code version: 2.1.243
  • Install method: curl -fsSL https://claude.ai/install.sh | bash
  • Platform key resolved by installer: linux-x64

Steps to Reproduce

curl -fsSL https://claude.ai/install.sh | bash

or, running the downloaded binary directly:

DOWNLOAD_BASE_URL="https://downloads.claude.ai/claude-code-releases"
version=$(curl -fsSL "$DOWNLOAD_BASE_URL/latest")
curl -fsSL -o ./claude "$DOWNLOAD_BASE_URL/$version/linux-x64/claude"
chmod +x ./claude
./claude install

Actual Behavior

The installer/binary segfaults immediately:

Setting up Claude Code...
bash: line 226:  1151 Segmentation fault         "$binary_path" install ${TARGET:+"$TARGET"}
Installation was killed before it could finish (exit code 139).

dmesg shows a deterministic null-pointer dereference at the same code offset on every run:

claude-2.1.243-[...]: segfault at 0 ip 0000000001d10458 sp ... error 4 in claude-2.1.243-linux-x64[1b0f458,1953000+3b33000] likely on CPU ...
claude-2.1.243-linux-x64: claude-2.1.243-: potentially unexpected fatal signal 11.

A gdb backtrace on the raw downloaded binary shows the crash is inside the binary's own statically-linked free(), called from the system glibc's newlocale():

Program received signal SIGSEGV, Segmentation fault.
0x0000000001d10458 in free ()
#0  0x0000000001d10458 in free ()
#1  0x00007ffff7c3530a in newlocale () from /usr/lib/libc.so.6
#2  0x0000000001abd282 in ?? ()
#3  0x00007ffff7c9d7fc in ?? () from /usr/lib/libc.so.6
#4  0x00007ffff7c9d879 in pthread_once () from /usr/lib/libc.so.6
...
#16 0x00007ffff7c27892 in __libc_start_main () from /usr/lib/libc.so.6

rax = 0x0   rcx = 0x0   rdi = 0x0

=> 0x1d10458 <free+40>:  mov    (%rax,%rcx,1),%rdi   ; RAX is a NULL table pointer
   0x1d1045c <free+44>:  test   %rdi,%rdi
   0x1d1045f <free+47>:  je     0x1d1047f <free+79>

RAX (loaded via a rip-relative global at the top of free) is NULL, i.e. the binary's own heap-metadata table was never initialized for this allocation — consistent with free() being invoked on a pointer that the system libc's own malloc allocated internally (inside newlocale/pthread_once locale bootstrap), not the binary's bundled allocator.

Expected Behavior

claude install completes successfully, as it does on other Linux distributions.

Root-Cause Confirmation

  1. Setting vm.mmap_rnd_bits=28 (ruling out an ASLR/ address-layout theory) had no effect — crash address (0x1d10458) and register state were byte-for-byte identical across runs, i.e. it's deterministic, not layout-dependent.
  2. Running the exact same downloaded linux-x64 binary inside a Docker container:
  • ubuntu:22.04 (glibc 2.35) → installs successfully, no crash.
  • Host Arch Linux (glibc 2.44) → segfaults every time.
  1. LC_ALL=C LANG=C env vars do not avoid the crash (still segfaults, same address).

This strongly suggests a regression/incompatibility introduced somewhere between glibc 2.35 and 2.44 in how newlocale() (or its internal pthread_once-guarded locale-category init path) interacts with a statically-linked allocator that overrides the process-wide malloc/free symbols.

Workaround

Downloading and running the musl build instead of the glibc build completely avoids the issue, since musl doesn't participate in this dynamic symbol interposition the same way:

sudo pacman -S musl   # official Arch `extra` repo, ~3.7MB, does not touch system glibc

version=$(curl -fsSL https://downloads.claude.ai/claude-code-releases/latest)
curl -fsSL -o /tmp/claude-musl "https://downloads.claude.ai/claude-code-releases/$version/linux-x64-musl/claude"
chmod +x /tmp/claude-musl
/tmp/claude-musl install

This installs and runs claude successfully on the same host.

Suggested Fix

  • Investigate why the bundled allocator's free() is reachable from libc.so.6-internal calls (e.g. newlocale) on newer glibc, and/or avoid globally exporting malloc/free symbols from the binary in a way that shadows the system libc's own internal allocations.
  • As a pragmatic short-term mitigation, install.sh / the updater could detect the glibc version at install time and prefer the -musl build when the detected glibc is newer than whatever version the binary was validated against, since the musl build is already published and unaffected.

Happy to provide the full binary, a core dump, or further gdb/objdump output if useful.

View original on GitHub ↗

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