Linux 内核将支持 $ORIGIN,某种程度上
出于某种原因,在 2026 年的 TacoSprint 中,我决定看看我们是否可以在 Nix 中处理可重定位的二进制文件。我喜欢这些宏伟的目标,以推动 Nix 及其周边生态系统向前发展。我大胆,甚至有些愚蠢。我在上一篇文章中留下了一个潜在的想法:我们可以修补 Linux 内核,以便在 PT_INTERP 和 shebang 中支持 $ORIGIN。我在发送补丁的复杂性中摸索(结果我发现我其实喜欢这种工作流程!),并向 Linux 内核邮件列表发送了一份提议。我的第一次尝试这里提议简单地在虚拟文件系统 (VFS) 子系统中直接支持 $ORIGIN。我紧张地等待。我原本期待的是从我在网上看到的结果;有人不客气地告诉我去 F$#CK OFF,因为我遗漏了、误解了或没有考虑到什么。🤬 结果完全不同。😲 VFS 的维护者 Christian Brauner 诚恳地回复了我,询问更改的理由,并最终提出了一些支持如何进入子系统的方法。值得注意的是,有像 John Ericson 这样的人出来提倡为什么拥有一个非固定的解释器 (PT_INTERP) 对 Nix 和其他用例(例如 Buck 和 Bazel)是有用的,确实很有帮助。他提供了一个可能性,我们可以利用 eBPF 作为一种可编程方式,通过 binfmt_misc 选择解释器。哇!🤯 我只是想允许 $ORIGIN,但可编程选择可以让我们实现任何想做的事!这个想法一定让他相当感兴趣,因为在他度假后不久,Christian 提出了这种解决方案的第一稿。我们在邮件列表上来回交流了一下,最终的结果是一系列补丁,将在不久的将来进入 -next 分支。如果你不知道 eBPF 或 binfmt_misc 是什么,那我们刚刚合作了什么?让我们来看看!我不会公正地解释 eBPF,网上有很多关于它的文章,因为目前它相当流行。简而言之,你可以在 C 的一个子集上编写程序,这些程序被编译成一个指令集,而其虚拟机在内核中运行。内核难道不应该非常快吗?是的,这些程序是即时编译为其本地 CPU 架构,并且程序有固定的时间片。这对内核来说难道不是一种疯狂的漏洞吗?在任何代码加载之前,它被“验证”为安全的。查看这个指南以获取更多信息。我们现在可以用一个相对简单的 eBPF 程序支持 $ORIGIN:SEC ("struct_ops.s/match") bool BPF_PROG (nix_match, struct linux_binprm *bprm) { return !bpf_strncmp(bprm->buf, 4, "\x7f" "ELF"); } SEC ("struct_ops.s/load") int BPF_PROG (nix_load, struct linux_binprm *bprm) { char path[256]; long n; n = bpf_path_d_path(&bprm->file->f_path, path, sizeof(path)); if (n < 0) return n; /* derive the loader location from the binary's path */ return bpf_binprm_set_interp(bprm, path, sizeof(path)); } SEC (".struct_ops.link") struct binfmt_misc_ops nix = { .match = (void *) nix_match, .load = (void *) nix_load, .name = "nix", }; 一旦上述程序被加载并注册到内核中,我们就要求 binfmt_misc 子系统触发它。如果你想查看完整的示例,可以查看这个线程。> bpftool struct_ops register nix_origin.bpf.o /sys/fs/bpf > echo ':origin:B::::nix:' > /proc/sys/fs/binfmt_misc/register 这意味着什么?这意味着每个二进制文件现在都触发上述 nix_match 函数,就这个例子而言是任何 ELF 文件,但也可以是带有新段如 PT_INTERP_NIX 的可执行文件,内核将询问 nix_load 动态确定要使用的解释器。我们的特定 BPF 程序支持 $ORIGIN 💥 你还能做什么?实际上我们现在甚至可以用一个 BPF 程序完全替换传统的 QEMU binfmt_misc 注册脚本,如这个。我们还可以做什么?因为我们现在可以根据文件中的任何东西以编程方式选择我们的解释器,所以我们能够做很多事情。我很想听听你的建议和想法 💡。一些小的项目是,我们甚至可以非常轻松地在 shebang 中支持 $ORIGIN (#!$ORIGIN/bin/ld.so),正如这里所示:我们只需查看文件的前 256 个字节并查找 $ORIGIN 以触发。传统的 binfmt_misc 移交的一个缺点或副作用是,所请求的最终二进制文件的调用方式并不透明。注册的解释器成为了进程。它拥有整个进程的身份,而你实际请求运行的二进制文件则被降级为一个参数。对于 wine 或 qemu 来说,这可以接受,因为它们是模拟器,但对于可能选择传统 ld.so 的按二进制文件的 BPF 加载器来说,这并没有太多意义。这在几个痛苦的方面泄漏,但最简单的是:argv[0] 和 /proc/<pid>/cmdline 显示的是解释器调用,而不是你执行的内容。/proc/self/exe 命名了解释器。可重定位程序通常通过 /proc/self/exe 定位自己。
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡