Linux binfmt_misc 内核机制全解:从魔数匹配到 BPF 解释器选择 📅 发布时间:2026/9/7 19:44:49 👁 浏览次数: Linux binfmt_misc 内核机制全解从魔数匹配到 BPF 解释器选择【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linuxbinfmt_misc 是 Linux 内核的“杂项二进制格式”挂载文件系统它允许你在 shell 中直接敲文件名就执行 Java、Python、Emacs、Windows PE 等任意自定义格式的程序——内核通过魔数magic或扩展名识别文件类型并把执行权交给你注册的解释器。本篇基于内核文档 Documentation/admin-guide/binfmt-misc.rst 与对应实现源码完整讲解注册字符串协议、P/O/C/F/T/L/D 七个标志位的语义与限制、启停管理方法以及CONFIG_BINFMT_MISC_BPF带来的 BPF struct_ops 处理器、透明解释器T与加载器替换L两套新执行模型。一、binfmt_misc 解决的问题与识别原理binfmt_misc 让你“几乎”具体限制见第六节可以把任何程序当可执行文件直接运行。原理是你必须先告诉 binfmt_misc “某类二进制应该由哪个解释器来跑”。内核识别二进制类型有两种方式魔数匹配M读取文件开头若干字节与一段你提供的 magic 字节序列比对可对指定位做掩码扩展名匹配E按文件名后缀识别例如.com、.exe。识别命中后内核把该文件交给注册字符串中指定的解释器程序执行。典型场景包括在 DOSEMU 下运行 DOS 程序、用 Wine 运行 Windows 可执行文件、用 Java/Python 脚本直接./myscript启动。内核配置与挂载对应配置项定义在 fs/Kconfig.binfmtCONFIG_BINFMT_MISCtristateKernel support for MISC binaries编译进内核Y或做成binfmt_misc模块M都可以CONFIG_BINFMT_MISC_BPFbool启用 BPF struct_ops 处理器要求BINFMT_MISCy并依赖BPF_SYSCALL、BPF_JIT与DEBUG_INFO_BTF。使用前必须先挂载 binfmt_misc 文件系统mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc也可以写入/etc/fstab实现开机自动挂载none /proc/sys/fs/binfmt_misc binfmt_misc defaults 0 0挂载后可在/proc/sys/fs/binfmt_misc/下看到每个注册项对应的同名文件以及用于注册的register文件与全局开关status文件。二、注册协议逐字段拆解 register 字符串注册一个新的二进制类型需要把一个形如:name:type:offset:magic:mask:interpreter:flags的字符串写入echo 到/proc/sys/fs/binfmt_misc/register字段分隔符:可按需选用。各字段含义如下字段含义name标识字符串会在/proc/sys/fs/binfmt_misc下生成同名文件不能包含/type识别类型M魔数、E扩展名、BBPF 支撑的处理器见第七节offsetmagic/mask 在文件中的字节偏移可省略默认为 0即写成:name:type::magic...扩展名匹配时忽略magic待比对的字节序列可用十六进制转义如\x0a、\xA4。NUL 字节必须转义解析在第一个 NUL 处终止shell 中有时要写\\x0a防止反斜杠被 shell 吃掉。扩展名匹配时此字段是扩展名本体不带.不允许\x0a转义扩展名匹配区分大小写且不允许含/mask可选掩码默认全0xff。格式与 magic 相同、长度须与 magic 一致内核将 mask 与文件字节序列做 AND 后再比较。NUL 同样必须转义扩展名匹配时忽略interpreter要启动的解释器程序二进制文件作为其第一个参数传入必须给出完整路径。B类型时此字段是 BPF 处理器名称flags可选字段由若干大写字母组成控制解释器的启动方式见下节魔数比对逻辑可在 fs/binfmt_misc.c 的entry_matches_magic()中看到取bprm-buf e-offset处的字节在设置 mask 时先与 mask 做 AND 再与 magic 比较。flags 各标志位详解flags是可选字段由大写字母串组成每个字母控制解释器调用的一个方面Ppreserve-argv[0]binfmt_misc 的遗留行为是用二进制的完整路径覆盖原始argv[0]。带上此标志后内核改为追加一个参数来传递该路径从而保留调用者的原始argv[0]。例如解释器设为/bin/foo你运行的是/usr/local/bin/blah则内核执行/bin/foo时argv[]为[/bin/foo, /usr/local/bin/blah, blah]解释器需要自行处理这一约定并以argv[] [blah]执行二进制。Oopen-binary遗留行为是把二进制完整路径作为参数传给解释器。带上此标志后内核把文件以只读方式打开并将文件描述符通过AT_EXECFD传入 aux vector使解释器能够执行不可读比如 setuid 场景下路径不可访问的二进制。需谨慎使用——解释器必须被信任不得泄露不可读二进制的内容。Ccredentials默认情况下新进程的信令和 security token 按解释器计算带上此标志后按二进制计算并隐含O。同样需谨慎以 binfmt_misc 运行 root 所有的 setuid 二进制时解释器将以 root 权限运行。Ffix binary默认情况下解释器在调用 misc 格式文件时才懒加载打开。这在 mount namespace / chroot 场景下表现不佳F模式在注册时就打开二进制并保存已打开的镜像此后无论环境如何变化挂载点、路径变化解释器始终可用。Ttransparent透明执行。二进制经AT_EXECFD交给解释器T隐含O参数向量原封不动保留为调用者构造的样子内核把/proc/pid/exe标注为二进制而非解释器。解释器必须从AT_EXECFD加载二进制并遵守AT_FLAGS_TRANSPARENT_INTERP契约详见第八节。T与P组合会被拒绝透明模式保留整个参数向量含argv[0]二者语义冲突。Lloader substitution根本不把二进制交给解释器运行而是把二进制本身作为完整原生 exec 的主映像加载并用注册的解释器替换二进制PT_INTERP段里声明的加载器详见第九节。L拒绝T、P、O、C与F可组合。Dregistered disabled条目以“禁用”状态创建必须先向其 proc 文件写1启用后才参与分派。这把“创建条目”和“激活条目”拆成两步中间留出配置窗口——这正是绑定解释器的B条目所需要的见第七节。D在注册时即被消耗不会被读回条目文件之后报告的是当前启停状态。在核心数据结构上这些标志对应 include/linux/binfmts.h 中的BINPRM_FLAGS_PRESERVE_ARGV0、BINPRM_FLAGS_TRANSPARENT_INTERP等位bprm_at_flags()内联函数负责把本次执行采用的契约通过AT_FLAGSaux vector 条目AT_FLAGS_PRESERVE_ARGV0或AT_FLAGS_TRANSPARENT_INTERP告知用户态接收端。三、注册示例假设当前位于 /proc/sys/fs/binfmt_misc1. 启用 em86 支持Alpha AXP 专属的 x86 仿真器echo :i386:M::\x7fELF\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03:\xff\xff\xff\xff\xff\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfb\xff\xff:/bin/em86: register echo :i486:M::\x7fELF\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06:\xff\xff\xff\xff\xff\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfb\xff\xff:/bin/em86: registermagic 是7f ELF魔数加字节序/机器号字段的组合mask 中0xfe/0xfb处用于掩掉 e_flags 等无关位。2. 启用打包 DOS 应用预配置的 dosemu hdimageecho :DEXE:M::\x0eDEX::/usr/bin/dosexec: register3. 用 Wine 运行 Windows 可执行文件echo :DOSWin:M::MZ::/usr/local/bin/wine: registerPE 文件以MZ两个字节开头是最典型的短魔数示例。Java 支持的完整配置可参考 Documentation/admin-guide/java.rst.NET/Mono 参考 Documentation/admin-guide/mono.rst。四、启停、删除与持久化启用/禁用向/proc/sys/fs/binfmt_misc/status全局或/proc/.../the_name单个条目写入0禁用、1启用直接cat条目文件可查看当前状态。删除向/proc/.../the_name或/proc/sys/fs/binfmt_misc/status写入-1删除单个条目或全部条目单个条目也可以直接rmunlink对应的 proc 文件删除。开机注册可以在/etc/rc系列脚本中加入注册命令让二进制格式在启动时自动登记具体做法请参考所用 init 程序的文档。顺序很重要后注册的条目先匹配——内核按注册的逆序遍历条目第一个命中的生效。五、限制条件文档列出的硬性限制在 fs/binfmt_misc.c 中可看到其来源限制数值/规则注册字符串总长不得超过1920 字符MAX_REGISTER_LENGTH由 7 个分隔符 name/type/offset/magic/mask/interp/flags 各字段上限约出magic 位置必须位于文件前128 字节内即offset size(magic) 128解释器路径长度不得超过127 字符C/L且无F的解释器必须是绝对路径。它在执行二进制时才打开相对路径会按运行者的工作目录解析F预打开或绑定到B条目的解释器数量受 sysctl/proc/sys/user/max_binfmt_misc_interpreters限制超限注册返回-ENOSPC。该限制用于约束非特权 namespace 的文件固定pinning嵌套 namespace 只能抬高自己的上限且每个祖先 namespace 也会被计入配额六、BPF 支撑的处理器CONFIG_BINFMT_MISC_BPF启用该配置后匹配和解释器选择都可以委托给 BPF 程序。处理器handler是一个binfmt_misc_opsstruct_ops 实例包含match和load两个程序以及一个name。struct_ops map 注册成功后可用一个在interpreter字段引用处理器名称的B条目来激活它——该条目不携带offset、magic、maskecho :qemu:B::::my_handler: register实现位于 fs/binfmt_misc_bpf.c两个 BPF 程序都收到二进制的linux_binprm且都可以睡眠sleepable源码在 bm_bpf_check_member 中强制要求match/load必须为 sleepable 程序理由是 exec 时可靠的文件读取需要睡眠。match程序决定处理器是否适用它在条目遍历中被咨询与魔数/扩展名匹配一样按注册顺序走、遵循同样的 first-match-wins 语义但与静态匹配不同它不受bprm-buf中预取前缀字节的限制可以读取文件、例如解析位于任意偏移处的 ELF 程序头。它只做决策下面的选择类 kfunc 在match中被拒绝kfunc 过滤器 bm_bpf_kfunc_filter 只放行load程序。命中后的load程序再选择解释器它同样可以读文件、根据二进制位置推导解释器并通过调用bpf_binprm_set_interp()kfunc 传入绝对路径并返回0完成选择实现见 bpf_binprm_set_interp路径必须以/开头、长度小于PATH_MAX。一旦命中即承诺load失败会带着其错误码使整个 exec 失败而不是回落到后面的条目返回-ENOEXEC则让其余二进制格式继续尝试。这样选出的路径按执行 exec 的任务的凭据打开与静态注册的无F解释器行为一致。绑定解释器Dname path写入条目也可以直接绑定处理器可能使用的解释器使 exec 时完全不发生路径解析任何 namespace 的路径变化都无法把解释器重定向到别处。以D注册的条目尚不可匹配这正是留给它绑定解释器的窗口每次写入一个name pathecho :qemu:B::::my_handler:D register echo aarch64 /usr/bin/qemu-aarch64 qemu echo arm /usr/bin/qemu-arm qemu echo 1 qemu每条路径在其写入时刻打开处于写入进程的上下文、以条目文件被打开时的凭据打开与F为静态条目预打开解释器的方式完全相同路径必须是绝对路径。路径是第一个空格之后的全部内容因此任何路径都能表达也不必再把解释器塞进注册字符串。一个条目最多绑定100个解释器每一个都计入max_binfmt_misc_interpreters配额任一限制超出的写入返回-ENOSPC。随后load程序用bpf_binprm_select_interp()kfunc 按名字为每次 exec 选一个实现见 bpf_binprm_select_interp未绑定的名字返回-ENOENT命中则带上已打开文件的引用每次 exec 运行的是当时打开的文件的一个 clone。路径只决定绑定哪个文件此后不再解析。启用即封存条目启用后exec 时读取其解释器只保留一个对条目的引用所以一旦条目曾经可匹配其解释器集合就永远不可再改——第一个1将其封存此后写入被-EBUSY拒绝未带D注册的条目从一开始就是封存的。重复绑定同名解释器返回-EEXIST。按名字选择让配置与程序无需约定顺序也让处理器不必依赖发行版把解释器放在哪个目录。名字是单个可打印 ASCII 单词最长 32 字符。解释器以注册时的路径运行条目报告其绑定内容$ cat /proc/sys/fs/binfmt_misc/qemu enabled bpf my_handler bpf-interpreter aarch64 /usr/bin/qemu-aarch64 bpf-interpreter arm /usr/bin/qemu-arm flags:报告的路径是解释器被绑定时命名的文件它不会被重新解析——是绑定记录而非对当前路径内容的承诺。load 程序可用的 kfunc 一览kfunc作用bpf_binprm_set_interp()按绝对路径选择解释器exec 时按当前任务凭据打开bpf_binprm_select_interp()按名字选择条目已绑定的解释器无 exec 时路径解析bpf_binprm_set_interp_arg()给解释器传单个参数插入在解释器与二进制之间语义等同#!解释器行的可选参数如处理器解析脚本#!路径中的$ORIGIN时需保留其后跟的参数见 bpf_binprm_set_interp_argbpf_binprm_set_flags()按 exec 选择调用标志见 bpf_binprm_set_flags静态条目在注册时固定的调用标志P、C、O、T、L对 BPF 处理器来说是每次 exec 的运行时选择由load程序通过bpf_binprm_set_flags()决定于是同一个处理器可以对不同二进制做不同决策BPF_BINPRM_PRESERVE_ARGV0保留调用者的argv[0]即PBPF_BINPRM_CREDENTIALS按二进制计算信令即C约束在与任何 setuid exec 相同的“用户 namespace 必须映射二进制属主”规则之内BPF_BINPRM_EXECFD替解释器打开二进制并经由AT_EXECFDaux vector 条目传入即O使解释器能运行它按路径打不开的二进制BPF_BINPRM_TRANSPARENT透明执行即T二进制经AT_EXECFD移交如同BPF_BINPRM_EXECFD同时参数向量保持调用者原样——从AT_EXECFD加载二进制的解释器在argv[0]和/proc/pid/cmdline中呈现为对二进制的直接执行。BPF_BINPRM_PRESERVE_ARGV0与暂存的解释器参数都不得与它组合与PT的拒绝同理。它还让处理器能执行经execveat()传入的不可访问O_CLOEXEC文件描述符二进制——按路径拼接的分派做不到这点因为解释器没有可用路径去打开它BPF_BINPRM_LOADER用解释器替换二进制的PT_INTERP把二进制作为完整原生 exec 运行即L与其他标志及暂存的解释器参数互斥。因为这些是程序的选择B条目的注册字符串不携带调用标志F对B条目也无从谈起——它绑定的解释器已提前打开好F要做的事。注册指令D是例外它决定条目以什么状态开始而非解释器如何被调用。命名空间规则处理器只在 struct_ops map 注册所在的同一用户 namespace内被查找不被继承——条目只能引用与其 binfmt_misc 实例同 namespace 注册的处理器。条目持有处理器使其存活删除 struct_ops map 只是阻止新的激活。七、透明解释器T/BPF_BINPRM_TRANSPARENT透明标志让分派对最终进程完全不可见参数向量保持调用者构造的原样二进制经AT_EXECFD传递内核正确标注/proc/pid/exe进程运行期间二进制的文件被拒绝写入而解释器的不被拒绝——与直接执行二进制完全一致透明条目不改变信令推导方式与其他条目一样二进制的 set*id 位只有配合C或BPF_BINPRM_CREDENTIALS才生效。解释器必须按此契约构建。内核通过在AT_FLAGSaux vector 条目紧邻AT_EXECFD中给出AT_FLAGS_TRANSPARENT_INTERP位来宣告契约——该标志在 include/linux/binfmts.h 中定义。参数向量完全属于程序、没有被拼接任何东西因此解释器不消耗参数直接从描述符加载程序。该位同时是加载器“补全身份”的许可映射程序后它可以经由一次PR_SET_MM_MAP重定向/proc/pid/auxv的AT_PHDR/AT_ENTRY/AT_BASE条目以及 code/data 统计标记从而补全调试器 attach 时所见的一切。仍与直接执行可见不同之处只有地址空间布局解释器占据主映像位置程序位于 mmap 区域。八、加载器替换LL把执行模型翻了个个儿内核不再以二进制为载荷运行注册的解释器而是把匹配的二进制本身作为主映像加载并用注册的解释器替换二进制PT_INTERP中声明的加载器。因为 exec 是原生的不存在需要重建的分派身份也没有契约要求替代加载器实现——标准动态加载器无需任何改动。参数向量原封不动信令和AT_SECURE按二进制推导没有AT_EXECFD、aux vector 中无标记二进制位于主映像槽位、带原生 brk 布局因此/proc/pid/maps、core dump 与 perf mmap 记录都呈原生形态PTRACE_EVENT_EXEC暂停被跟踪进程时身份已完整——所以在调试器下启动也正常工作而不仅仅是 attach。L条目面向原生架构的 ELF 二进制异构架构仿真与非 ELF 载荷仍是经典模式和透明模式的领域。生效条件与边界当最终认领该文件的格式是带PT_INTERP的 ELF 时替换才生效。无PT_INTERP的匹配二进制或无解释器的ET_DYN会丢弃替换并按原生运行被其他格式认领的文件比如#!脚本按该格式处理如同条目未匹配。因此L不是强制机制它决定“要求加载器的二进制”如何运行不保证匹配该条目的一切都在替代加载器下运行。完全无法消费替换的格式则会在“不可回退点”之前以ENOEXEC拒绝 exec。错误语义错误架构的 ELF 使整个 exec 以ENOEXEC失败与无条目匹配时相同替代加载器若不是正确架构的 ELF 则报ELIBBAD对二进制本身的常规PT_INTERP健全性检查依然适用但段内容之外的部分无关紧要。注册时约束L在注册时拒绝经典分派标志T、P、O、CF可组合且很有价值——带上它替代加载器在注册时即被打开此后 mount namespace 或路径变化都无法重定向它不带则替代加载器在执行二进制时才打开路径在运行者的 mount namespace 与 root 中解析——这就是它必须是绝对路径的原因。如同C一样只注册可信的解释器被替换的加载器以按二进制推导的信令运行。九、实用提示Hints想给解释器传特殊参数可以给它写一个包装脚本。示例见 java.rst。你的解释器不应该去PATH里查找文件名内核传给它的就是完整文件名或文件描述符。依赖$PATH可能引起意外行为也可能成为安全隐患。十、延伸阅读路径内容仓库路径本文档Documentation/admin-guide/binfmt-misc.rst注册/匹配/条目管理核心实现fs/binfmt_misc.cBPF 处理器struct_ops、kfunc、命名空间查找fs/binfmt_misc_bpf.cKconfigBINFMT_MISC/BINFMT_MISC_BPFfs/Kconfig.binfmtlinux_binprm、binfmt_misc_bpf选择状态与AT_FLAGS映射include/linux/binfmts.hbinfmt_misc 内部头文件include/linux/binfmt_misc.hJava / Mono 集成示例java.rst、mono.rst【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考