Linux Outsources the $ORIGIN Problem to BPF
A 21-patch binfmt_misc series makes interpreter selection programmable, finally giving Nix-style relocatable binaries a real path.
For as long as ELF has existed on Linux, there's been one absolute path you couldn't escape: PT_INTERP, the header field that names the dynamic loader. The $ORIGIN token — "the directory this binary lives in" — works fine in RUNPATH for finding libraries, because by then ld.so is running in userspace and can expand strings. But ld.so itself is mapped by the kernel during execve(), before any userspace code exists to do the expanding. So every dynamically linked binary on your system hardcodes something like /lib64/ld-linux-x86-64.so.2, and truly relocatable binaries have remained a hack-stack of workarounds.
That's now changing — though not the way anyone asked for. In June, Farid Zakaria (of Nix ecosystem and shrinkwrap fame) sent a two-patch series to the kernel lists proposing literal $ORIGIN support in the ELF interpreter path. VFS maintainer Christian Brauner's counteroffer, which landed on linux-fsdevel on July 20 as a 21-patch series titled "binfmt_misc: transparent interpreters," does something more interesting: it makes interpreter selection programmable via eBPF. You don't get $ORIGIN. You get the machinery to implement $ORIGIN — or any other interpreter-selection policy — yourself.
Why the kernel never just expanded the string
The naive fix looks trivial: teach load_elf_binary() to substitute $ORIGIN before opening the interpreter. The kernel has refused this for decades, and the reasons are good ones.
Path expansion in execve() is a security minefield, and we have the scar tissue to prove it. In 2010, glibc's own $ORIGIN handling in privileged programs produced CVE-2010-3847: hardlink a setuid binary into a directory you control, and $ORIGIN resolves to your directory — your loader now runs with elevated privileges. Any kernel-side $ORIGIN would have to re-litigate that entire class of attack for setuid, fscaps, and LSM-constrained binaries. Beyond security, kernel maintainers have a healthy allergy to string-templating languages in syscall paths: today it's $ORIGIN, tomorrow it's $LIB and $PLATFORM and a substitution engine nobody can remove.
Brauner's move sidesteps both objections. Instead of the kernel owning the policy, a BPF program registered as a struct_ops handler hooks into binfmt_misc — the same subsystem that's been dispatching Wine and qemu-user binaries since the 90s. The BPF program inspects the binprm, derives the loader path (kfuncs like bpf_path_d_path plus a new bpf_binprm_set_interp do the heavy lifting), and tells the kernel what to load. A new dispatch mode means binfmt_misc no longer has to hand off execution wholesale: the kernel runs the matched binary natively and merely swaps the loader named in PT_INTERP. Registration looks like this:
bpftool struct_ops register nix_origin.bpf.o /sys/fs/bpf
echo ':origin:B::::nix:' > /proc/sys/fs/binfmt_misc/register
The verifier bounds what the program can do, and the policy lives in root-controlled userspace, not in fs/binfmt_elf.c forever.
execve joins the BPF-policy club
Squint and this is the same story as sched_ext, BPF-LSM, and HID-BPF: when someone asks the kernel to adopt a policy, the answer is increasingly "no, but here's a hook." Scheduling policy left the kernel, security policy left the kernel, and now interpreter selection is leaving too. You can read that as maintainer wisdom or as responsibility laundering, but it has a concrete consequence — the feature only exists on machines where someone has installed the policy. A binary that depends on a registered BPF handler isn't portable in the "scp it anywhere" sense. It's relocatable on fleets you control.
That distinction decides who actually benefits. NixOS itself is the obvious winner: ld.so is version-locked to its glibc, a single Nix store legitimately contains several glibcs, and today every binary gets its store-pathed loader baked in via patchelf --set-interpreter. Because /nix/store paths feed the hashes, moving the store — for rootless Nix, or Bazel sandbox integration — cascade-invalidates the world and triggers hours of rebuilds. A $ORIGIN-style handler breaks that coupling. Zakaria says a NixOS module is planned once the series lands, gated on a new opt-in ELF segment so the handler ignores ordinary binaries. The same pain exists outside Nix: Spack maintains its sbang shim and relocation machinery for HPC users installing into home directories, and Python venvs break on the same absolute-shebang problem — notably, the series also covers $ORIGIN-style resolution for script shebangs, an arguably bigger audience than PT_INTERP.
If you're not running Nix, Spack, or a hermetic-build fleet, your action item is different: put this on the security-review list. binfmt_misc registration was already a known persistence trick in over-privileged containers, and "root can install a program that silently redirects which loader every matching binary gets" is exactly the kind of primitive detection engineers should model before it ships in a distro near them. It's root-only, and the verifier constrains the program — but auditing execve behavior now means auditing BPF state too.
Real, but not yet, and not for everyone
The honest status: Brauner's series is on the mailing list as of July 20 and, per Zakaria, headed for linux-next "in the near future" — which means no released kernel has it, and distro kernels are realistically a year-plus out. The design also drew fair pushback on Hacker News: per-binary loaders strike some as an anti-pattern versus one system loader, though that critique mostly ignores why Nix can't have one system loader in the first place.
My read: this is a genuine infrastructure shift for a real problem, delivered in the most kernel-idiomatic way possible — and the "sort of" in Zakaria's framing is load-bearing. Linux isn't adopting $ORIGIN; it's declining to, permanently, while making it something you can build in an afternoon of BPF. For Nix, Spack, Bazel, and anyone shipping relocatable toolchains to machines they administer, that's the unblock they've wanted for a decade. For the "download a binary, run it anywhere" dream, nothing changed: the answer there is still static linking, AppImage-style self-extraction, or shipping the loader invocation yourself. The kernel didn't solve relocatable binaries. It just stopped being the excuse.
Sources & further reading
- Linux kernel will support $ORIGIN, sort of — fzakaria.com
- Nix needs relocatable binaries — fzakaria.com
- [PATCH 00/21] binfmt_misc: transparent interpreters — lore.kernel.org
- [PATCH 0/2] fs: support $ORIGIN in ELF interpreter path — lore.kernel.org
- Linux kernel will support $ORIGIN, sort of - discussion — news.ycombinator.com
Emeka has spent over a decade tracking threat actors, vulnerability disclosures, and the evolving landscape of application security, bringing a sharp continent-spanning perspective to his reporting. He's known for translating dense CVE advisories into clear, actionable context that developers and security teams alike actually read.
Discussion 0
No comments yet
Be the first to weigh in.