]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
binfmt_misc: let a bpf handler request loader substitution
authorChristian Brauner <brauner@kernel.org>
Tue, 21 Jul 2026 14:14:01 +0000 (16:14 +0200)
committerChristian Brauner <brauner@kernel.org>
Mon, 3 Aug 2026 08:08:47 +0000 (10:08 +0200)
Give bpf handlers the per-exec equivalent of the static 'L' flag. A
load program that sets BPF_BINPRM_LOADER has its selected interpreter
substituted for the binary's PT_INTERP instead of run with the binary
as payload. The binary otherwise executes as a fully native exec.

A single handler can now grade its dispatch per binary: native-arch ELF
with PT_INTERP gets loader substitution for full native identity.
Anything else, such as foreign arch, static, non-ELF can use transparent
or classic dispatch. The load program can read the binary's ELF header
from bprm->buf to make that call.

Link: https://patch.msgid.link/20260721-work-bpf-binfmt_misc-ptinterp-v2-19-e57866e4ae0f@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Documentation/admin-guide/binfmt-misc.rst
fs/binfmt_misc.c
fs/binfmt_misc_bpf.c
include/linux/binfmt_misc.h

index 22aefab2c21ec013d53c6511ead87bf57dc411c7..03c6806785f571bd644bdf737cf9f462f1050521 100644 (file)
@@ -197,9 +197,9 @@ interpreter line, e.g. for a handler that resolves ``$ORIGIN`` in a script's
 ``#!`` path and needs to preserve the argument that followed it.
 
 The invocation flags a static entry fixes at registration - ``P``, ``C``,
-``O`` and ``T`` - are per-exec choices for a bpf handler, made by the ``load``
-program with the ``bpf_binprm_set_flags()`` kfunc, so a single handler can
-decide them differently for each binary it handles:
+``O``, ``T`` and ``L`` - are per-exec choices for a bpf handler, made by the
+``load`` program with the ``bpf_binprm_set_flags()`` kfunc, so a single
+handler can decide them differently for each binary it handles:
 
 - ``BPF_BINPRM_PRESERVE_ARGV0`` keeps the caller's ``argv[0]`` (the ``P``
   flag).
@@ -220,6 +220,9 @@ decide them differently for each binary it handles:
   run a binary passed as an inaccessible ``O_CLOEXEC`` file descriptor to
   ``execveat()``, which a path-splicing dispatch cannot: the interpreter
   has no path by which to open it.
+- ``BPF_BINPRM_LOADER`` substitutes the interpreter for the binary's
+  ``PT_INTERP`` and runs the binary as a fully native exec (the ``L``
+  flag). It excludes the other flags and a staged interpreter argument.
 
 Because these are program choices, a ``B`` entry carries no flags in the
 register string; ``F`` (pre-open a fixed interpreter) has no meaning for it.
index 33835aebc8ebee7b2feb5dd1eaa113599c1d50dc..707f8a14f8a634c837533c1d2750a3fe574dcf59 100644 (file)
@@ -355,6 +355,8 @@ static unsigned long entry_invocation_flags(const struct binfmt_misc_entry *e,
                flags |= MISC_FMT_CREDENTIALS | MISC_FMT_OPEN_BINARY;
        if (bpf_flags & BPF_BINPRM_TRANSPARENT)
                flags |= MISC_FMT_TRANSPARENT | MISC_FMT_OPEN_BINARY;
+       if (bpf_flags & BPF_BINPRM_LOADER)
+               flags |= MISC_FMT_LOADER;
 
        return flags;
 }
index d279ffa9c4ad352057947976485156c75bc4e643..5bf0e46b867c4b8360bcee82dcda41af1539603f 100644 (file)
@@ -171,13 +171,15 @@ __bpf_kfunc int bpf_binprm_set_interp_arg(struct linux_binprm *bprm,
  * @flags: an OR of enum bpf_binprm_flags values
  *
  * To be called from the load program of a struct binfmt_misc_ops handler. It
- * decides per exec what a static entry fixes at registration with the P, C, O
- * and T flags: BPF_BINPRM_PRESERVE_ARGV0 keeps the caller's argv[0],
+ * decides per exec what a static entry fixes at registration with the P, C,
+ * O, T and L flags: BPF_BINPRM_PRESERVE_ARGV0 keeps the caller's argv[0],
  * BPF_BINPRM_CREDENTIALS computes credentials from the binary, and
  * BPF_BINPRM_EXECFD hands the binary to the interpreter through AT_EXECFD.
  * BPF_BINPRM_TRANSPARENT additionally leaves the argument vector untouched,
- * making the exec look like a direct execution of the binary. Calling it
- * again replaces the flags, passing zero clears them again.
+ * making the exec look like a direct execution of the binary.
+ * BPF_BINPRM_LOADER substitutes the interpreter for the binary's PT_INTERP
+ * and runs the binary as a native exec; it excludes every other flag.
+ * Calling it again replaces the flags, passing zero clears them again.
  *
  * Return: 0 on success, -EINVAL if @flags contains an unknown bit or an
  * invalid combination
@@ -186,7 +188,12 @@ __bpf_kfunc int bpf_binprm_set_flags(struct linux_binprm *bprm,
                                     enum bpf_binprm_flags flags)
 {
        if (flags & ~(BPF_BINPRM_PRESERVE_ARGV0 | BPF_BINPRM_CREDENTIALS |
-                     BPF_BINPRM_EXECFD | BPF_BINPRM_TRANSPARENT))
+                     BPF_BINPRM_EXECFD | BPF_BINPRM_TRANSPARENT |
+                     BPF_BINPRM_LOADER))
+               return -EINVAL;
+
+       /* Loader substitution is a native exec: no splice, execfd or creds work. */
+       if ((flags & BPF_BINPRM_LOADER) && (flags & ~BPF_BINPRM_LOADER))
                return -EINVAL;
 
        /* Transparency preserves the whole argv, argv[0] included. */
index 26da749391b43a83eecd4493bcbe27ecd3391035..4abdfd36b3fa820fb787a8d66170154a3e8de10c 100644 (file)
@@ -19,6 +19,9 @@ struct user_namespace;
  * @BPF_BINPRM_TRANSPARENT: leave argv untouched, the interpreter takes the
  *                          binary from AT_EXECFD (like the 'T' flag); implies
  *                          execfd, excludes preserve-argv0
+ * @BPF_BINPRM_LOADER: substitute the interpreter for the binary's PT_INTERP
+ *                     and run the binary as a native exec (like the 'L'
+ *                     flag); excludes every other flag
  *
  * Set from a load program with bpf_binprm_set_flags(). Unlike a static entry,
  * a bpf handler chooses these per exec rather than once at registration.
@@ -28,6 +31,7 @@ enum bpf_binprm_flags {
        BPF_BINPRM_CREDENTIALS          = (1ULL << 1),
        BPF_BINPRM_EXECFD               = (1ULL << 2),
        BPF_BINPRM_TRANSPARENT          = (1ULL << 3),
+       BPF_BINPRM_LOADER               = (1ULL << 4),
 };
 
 /**