Hello, The 'B' (BPF struct_ops) binfmt_misc handlers added in v7.3 can select an interpreter per exec. In transparent mode the selected interpreter is not visible through the usual interfaces: /proc/PID/exe, /proc/PID/cmdline and sched_process_exec all describe the binary, not the interpreter that actually runs. That is useful for concealment, and it also leaves tracing and security monitoring blind to the dispatch.
I built a small PoC to understand the mechanism and measure the gap. It registers three struct_ops handlers (classic, transparent, loader) and runs each target twice: once without a handler, once with the handler and entry active. In the transparent case the process sees /tmp/target_trans as its exe and keeps its argv, while the code that runs is /tmp/poc_interp, reached through AT_EXECFD. In the loader case the binary runs natively and only the PT_INTERP loader changes; /proc/self/maps and AT_BASE show the substitution. The match program checks the ELF magic and marker bytes in the e_ident padding; it does not check the machine type, so the PoC is architecture-independent and does not target a specific real binary format. The official binfmt_misc_bpf selftest passes 16/16 on the same kernel (v7.3-rc7). The test needed a small arm64 adaptation because it assumes an x86_64 host: the foreign-architecture constants and the interp_bind match rule were adjusted, while the assertions and coverage are unchanged. The gap is not a bug in the feature; it is a missing observation point. I would like to propose a tracepoint after the entry has selected its interpreter and the invocation flags are final: binfmt_misc_select(struct linux_binprm *bprm, const char *interp, const char *handler, unsigned long flags) The event would record filename, interp, handler, flags and pid. handler is the struct_ops name for a 'B' entry and empty for a static entry; flags is masked with MISC_FMT_INVOCATION_FLAGS so only the P/C/O/F/T/L bits are reported. The event is disabled by default and costs a static key check when off. It does not change dispatch behaviour. I tested a patch on v7.3-rc7-32-g0fcb05b72344 (2 files, 60 insertions). In an A/B run the event does not fire without a matching entry; with a classic handler it reports flags=0x0, and with a transparent handler it reports: binfmt_misc_select: pid=96 filename=/tmp/target_trans \ interp=/tmp/poc_interp handler=poc_transparent flags=0x48000000 while the process itself still sees /tmp/target_trans as its exe. The selftest stays at 16/16 with the patch applied. Before sending the patch, I would like to ask: - Is a tracepoint the right primitive here, or would you prefer another interface (audit, a /proc file, a BPF iterator)? - Is the event name and placement acceptable? I put it in fs/binfmt_misc.c and the header in include/trace/events/. - Should this go through the VFS tree or the tracing tree? - Is the flags mask the right ABI, or should the event report the raw entry flags and the resolved BPF flags separately? - Would you want this at all? The tracepoint is observability, not enforcement. tracefs is normally root-only, and a root-capable attacker can turn the event off; I am not claiming it stops a rootkit. If the approach is acceptable, I can send the patch as an RFC PATCH or a formal PATCH, rebased on whichever tree you prefer. If not, I would rather hear that now than send code that is not wanted. The PoC is attached as binfmt-poc-upstream.tar.gz (source and logs, about 70K). It includes an English README.upstream.md; some of the original notes are in Turkish. It needs v7.3+ with CONFIG_BINFMT_MISC_BPF=y and root inside the VM. The patch is not attached yet; I can send it inline on request. If the list strips the PoC attachment, I can send it directly. I prepared this email and the PoC with an LLM assistant; I reviewed the code and ran the tests above. Thanks, Eneshan Erdoğan Karaca
binfmt-poc (1).tar.gz
Description: application/gzip
