Linux 内核将支持 $ORIGIN,某种程度上

Hacker News Top 新闻

摘要

Linux 内核计划通过 eBPF 和 binfmt_misc 来支持可重定位二进制文件的 $ORIGIN,从而让 Nix 等工具能够以编程方式选择解释器。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/07/21 09:36

# Linux 内核将(某种程度上)支持 $ORIGIN 来源:https://fzakaria.com/2026/07/20/linux-kernel-will-support-origin-sort-of 不知为何,在 **TacoSprint 2026**(https://tacospring.org/)期间,我决定看看能否在 Nix 中攻克**可重定位二进制文件**(https://fzakaria.com/2026/06/21/nix-needs-relocatable-binaries)这一难题。我喜欢这些宏大的目标,它们推动 Nix 及其周边生态向前发展。我**虽傻但勇**。上一篇帖子末尾,我提出了一个可能的实现思路: > 我们可以修补 Linux 内核,让 `PT_INTERP` 和 shebang 支持 `$ORIGIN`。 我艰难地走通了通过电子邮件发送补丁的流程(结果发现我其实很喜欢这种工作方式!),并向 Linux 内核邮件列表发送了一份提案。我的**第一次尝试**(https://lore.kernel.org/all/[email protected]/)只是简单提议在虚拟文件系统(VFS)子系统中直接支持 `$ORIGIN`。我忐忑地等待着。预料中的结果是——正如我在网上看到的那样——有人会很不客气地让我“**滚蛋**”,因为我遗漏、误解或没有考虑到某些东西。🤬 结果完全出乎意料。😲 VFS 维护者 **Christian Brauner**(https://brauner.io/)诚恳地回复了我,询问变更的理由,并最终提出了一些可能将该支持纳入子系统的方案。 > **注意** > 有人帮忙说话确实很有帮助,比如 **John Ericson**(https://github.com/ericson2314)在邮件列表(https://lore.kernel.org/all/[email protected]/)中站出来解释:为何非固定解释器(`PT_INTERP`)对 Nix 以及其他用例(如 Buck 和 Bazel)非常有用。他提出,或许可以利用 **eBPF**(https://ebpf.io/)作为一种可编程的方式,通过 **binfmt_misc**(https://docs.kernel.org/admin-guide/binfmt-misc.html)来挑选解释器。 哇!🤯 我原本只是想允许 `$ORIGIN`,但可编程的挑选机制几乎能让我们做到任何事!这个想法显然深深吸引了他,因为不久之后——**在他度假期间**——Christian 就给出了该方案的第一版草稿。我们在邮件列表上来回讨论了几次,最终成果是一个**补丁系列**(https://lore.kernel.org/linux-fsdevel/20260716-wacholderbeere-zahlt-beraten-e872c3a4f59b@brauner/T/#ma8bbf0640f3154d76e1fd4607c61507b72609c6a),它将在不久的将来进入 `-next` 分支。 如果你还不了解 eBPF 或 `binfmt_misc`,那我们到底合作了啥?让我们来看看! 我不会全面介绍 eBPF(网上已有大量文章,因为它目前相当**流行**)。**一句话总结:** 你可以用 C 子集编写程序,编译成指令集,而它的虚拟机**运行在内核内部**。内核不应该超级快吗?是的,这些程序会被 JIT 编译为原生 CPU 指令,并且有固定的时间片。这难道不会给内核带来可怕的安全漏洞吗?任何代码在加载前都会经过“验证”以确保安全。更多信息请查看**这份指南**(https://ebpf.io/what-is-ebpf/)。 现在,我们可以用一个相对简单的 eBPF 程序来支持 `$ORIGIN`: ```c 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; /* 从二进制文件的路径推导加载器的位置 */ 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` 子系统触发它。如果希望看到完整示例,请查看**这个帖子**(https://lore.kernel.org/linux-fsdevel/[email protected]/)。 ```bash > 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` 注册脚本**(https://github.com/qemu/qemu/blob/master/scripts/qemu-binfmt-conf.sh),就像**这个**(https://gist.github.com/fzakaria/bef27d2e21b0e36ffccda1cbf417b636)一样。 还能做什么?既然我们可以**基于文件中的任何信息**以编程方式选择解释器,那就能做很多事了。我非常期待听到你的建议和想法 💡。一些小事情包括,我们甚至可以非常容易地在 shebang(`#!$ORIGIN/bin/ld.so`)中支持 `$ORIGIN`,就像**这里**(https://gist.github.com/fzakaria/2e1e1c44fa488a951674f8761c672366)展示的那样:只需查看文件的前 256 字节,寻找 `$ORIGIN` 来触发。 传统 `binfmt_misc` 移交的一个缺点或**副作用**是:调用目标二进制文件的方式是**非透明的**。注册的解释器**成为**了进程本身。它拥有完整的进程标识,而你原本想要运行的二进制文件被降级为一个参数。对于 `wine` 或 `qemu` 来说这是可以接受的(因为它们是模拟器),但对于一个可能挑选传统 `ld.so` 的按二进制 BPF 加载器而言,这就不太合理了。这种机制在一些令人头疼的方面暴露出来,最简单的包括: - `argv[0]` 和 `/proc/<pid>/cmdline` 显示的是*解释器的调用*,而不是你实际执行的程序。 - `/proc/self/exe` 指向的是解释器。可重定位程序通常通过 `/proc/self/exe` 定位*自身*,结果却找到了动态链接器。😩 Christian 也针对这个问题发送了一个大型补丁系列。他最新的**补丁系列**(https://lore.kernel.org/linux-fsdevel/20260720-work-bpf-binfmt_misc-ptinterp-v1-0-ddb76c9a508e@kernel.org/T/#m5c7c7cbf4e19d2f045a69f5a1284220d6c35d88c)添加了**两种**新的分发模式,从两端弥合了这一差距,并解决了其他几个这些模式可以修复的“陷阱”。 我最感兴趣的是**加载器替换**模式 `L`——这对 Nix 来说非常令人兴奋。使用 `L` 标志后,内核会**原生地**将匹配的二进制文件作为主镜像执行,仅仅将注册的解释器替换为二进制文件 `PT_INTERP` 中命名的加载器。`binfmt_misc` 不再是移交机制,而变成了简单的 `PT_INTERP` 覆盖。没有合约需要重建,也没有身份需要重建,因此**标准的动态加载器可以原样工作**。 这让我们走到了哪里?我会跟踪 Linux 内核的发布,一旦这个特性进入 `-next` 并随某个标记版本发布,我计划上游一个**NixOS 模块**,在启动时注册 `$ORIGIN` 支持。🎉 计划是通过一个新的 `PT_INTERP_NIX` 段来触发,而不是匹配每个 ELF 文件。这样可以保持**向后兼容**:BPF 处理程序只对明确选择加入(携带新段)的二进制文件生效。这意味着 Nix 生成的二进制文件在没有 BPF 处理程序的情况下仍能正常工作,但那些带有该段的二进制文件则可以提升到*可重定位状态*。 > 港湾里的船是安全的,但那不是造船的目的。 —— 约翰·A·谢德

相似文章

Nix 需要可重定位的二进制文件

Lobsters Hottest

文章指出了一个问题是 Nix 二进制文件不可重定位,当存储前缀改变时会导致哈希变化和重新编译,并提出使用带有 $ORIGIN 的相对路径在 RUNPATH 中实现可重定位,而不会使缓存失效。