When a BPF binfmt-misc handler runs an exec in transparent mode, why do the process exe link, comm, and command line still describe the binary instead of the interpreter that actually runs?
Because the kernel pins "identity" to the binary on purpose. A BPF binfmt-misc handler picks the interpreter through BPF-selected fields on struct linux_binprm; when the selection carries the transparent flag, the exec path swaps the code that runs to that interpreter but leaves the process identity — the exe link, the thread comm, and the argument vector — describing the binary. The interpreter decision is stashed in bprm->bpf_interp, bprm->bpf_interp_file, and bprm->bpf_flags, translated into MISC_FMT_* bits and then zeroed out inside the exec path. After exec completes, no user-visible interface and no tracepoint records which BPF handler made the choice or with which flags.
That is the whole reason the observation gap is structural, not an oversight in one place: the selection is a load-time, in-kernel fact that is deliberately not left on the process, so the standard exec observability tools are blind to the BPF side of it by design.
The mechanism
-
The BPF handler is a
struct binfmt_misc_opsstruct_ops map activated by a'B'binfmt-misc entry. Its load program is the only place the selection kfuncsbpf_binprm_set_interp,bpf_binprm_select_interp,bpf_binprm_set_interp_arg, andbpf_binprm_set_flagsare callable — the verifier restricts them to the map's load program. The chosen interpreter is stashed inbprm->bpf_interp/bprm->bpf_interp_fileand the flags inbprm->bpf_flags. -
load_misc_binarycallsentry_invocation_flags, which translates the BPF flags into theMISC_FMT_*invocation bits — the transparent flag maps toMISC_FMT_TRANSPARENTtogether withMISC_FMT_OPEN_BINARY— and then zeroesbprm->bpf_flagsso the flags do not accumulate into a nested interpreter level. On the transparent path it skipsbuild_interp_argv, callsbprm_change_interpto setbprm->interpto the interpreter string, and setsbprm->interpreterto the interpreter file withBINPRM_FLAGS_TRANSPARENT_INTERP. -
The
exec_binprmloop then runs the next round withbprm->file = bprm->interpreter— the interpreter becomes the next round's file. Whenbprm->have_execfdis set, the original binary is stashed asbprm->executablefor theAT_EXECFDhand-over to the interpreter, and the loop caps interpreter depth at 5 before returning-ELOOP. -
bprm_identity_filereturnsbprm->executable— the binary — wheneverBINPRM_FLAGS_TRANSPARENT_INTERPis set, andset_mm_exe_fileplus the comm derivation both feed from that. So/proc/PID/exeandcommpresent the binary whileargvis left untouched and the running code is the interpreter reached throughAT_EXECFD. The transparent mode is, in the kernel's own comment, the path that "update[s] interp for the next round" so the exec "look[s] like a direct execution."
How to verify and debug
-
Read the exec identity directly: compare
/proc/PID/exepluscommagainst/proc/PID/mapsand theAT_EXECFDauxv entry. In transparent mode the exe link,cmdline, andcommall describe the binary while the running code is the interpreter. -
Compare the two sched exec events on the same exec:
sched_prepare_execcarries theinterppath but still not the handler name or the flags, whilesched_process_exec— the event most tracers key off — carries onlyfilenamepluspidplusold_pid. The two report different things about the same exec, and the BPF handler name and selection flags are absent from both. -
Reproduce with a BPF handler: register the
'B'handler, create aD-state entry, bind an interpreter, then run an exec through it. The binary'sexelink stays, the argv stays, and the running code is the interpreter. -
Decode the observed flag value: a selection recorded as
0x48000000isMISC_FMT_TRANSPARENTorMISC_FMT_OPEN_BINARY— the translation inentry_invocation_flagsadds theOPEN_BINARYimplication that the transparent flag carries.
The limitation
-
The blind spot is not total:
sched_prepare_execdoes report the interpreter path. What no existing event or interface exposes is the BPF handler name and the selection flags. -
The identity behavior is intentional, not a bug. Transparent mode exists so the exec presents as the binary, which is why the exe link and comm follow the binary and
argvis preserved. -
The RFC's proposed default-off
binfmt_misc_selecttracepoint is observability, not enforcement. It reports the selection after the fact, and tracefs access is root-only, so root can turn it off. -
A BPF binfmt-misc handler is registered in its own user namespace and is not inherited from ancestor user namespaces, so which handler applied depends on the namespace the exec ran in.
-
Loader mode is a different mechanism: it substitutes the loader for the binary's
PT_INTERPand runs the binary natively, excluding every other flag — it is not the transparent path.
References
- Public bpf mailing-list RFC: a default-off
binfmt_misc_selecttracepoint — a public bpf-list RFC proposing a default-off tracepoint after the BPF handler's interpreter selection, reporting filename, interpreter, handler, masked flags, and pid, explicitly as observability rather than enforcement. - Kernel
fs/binfmt_misc_bpf.c— the BPF struct_ops handler kfuncs that stash the interpreter and flags. - Kernel
fs/binfmt_misc.c—load_misc_binary,entry_invocation_flags, and theMISC_FMT_*flag bits. - Kernel
fs/exec.c—bprm_identity_file,bprm_change_interp, and theexec_binprmloop. - Kernel
include/trace/events/sched.h—sched_process_execversussched_prepare_execfield lists.
Community discussion today
A public bpf mailing-list RFC proposes a default-off binfmt_misc_select tracepoint that fires after the BPF handler has selected its interpreter, reporting the filename, interpreter, handler, masked flags, and pid. It is framed as observability for the BPF-side selection — the exact fact that, as things stand, no existing tracepoint or user-visible interface exposes. The RFC is public and open for discussion on the list.
This run's archive-backed sources produced 0 messages in the 7-day window (the two opt-in archive sources were empty at snapshot time), so the question above was sourced from the permitted public bpf mailing list, reviewed through its public atom feed, and the RFC thread was used as the primary reference. The Discord channels for eunomia-bpf and sched-ext and r/eBPF were not covered this run (no visible-browser session was available), so they are marked not-covered rather than quiet.
Continue exploring
Back to index
eBPF Q&A
Daily, source-grounded answers to recurring questions about eBPF, Linux observability, profiling, runtime extension, and security.
Previous
Why does a local embeddings model with no server and no token count declare expected gaps in its GenAI span attributes, and how do the gaps stay spec-conformant?
The GenAI semantic conventions scope every span attribute to the availability of its value, not to the existence of the span.
Next
Why do non-sleepable BPF fentry and fexit programs silently skip calls from another task on the same CPU, and why do the skipped calls show up as recursion misses?
Because the trampoline recursion guard is per-CPU and is taken with preemption still enabled.
- Last updated
- Oct 24, 2026
- First published
- Oct 24, 2026
- Contributors
- Yunwei 123
Was this page helpful?