From: Christian Brauner <brauner@kernel.org>
To: Farid Zakaria <farid.m.zakaria@gmail.com>,
linux-fsdevel@vger.kernel.org
Cc: Daniel Borkmann <daniel@iogearbox.net>,
Alexei Starovoitov <ast@kernel.org>, Kees Cook <kees@kernel.org>,
Alexander Viro <viro@zeniv.linux.org.uk>,
Jan Kara <jack@suse.cz>, Jonathan Corbet <corbet@lwn.net>,
linux-mm@kvack.org, bpf@vger.kernel.org, jannh@google.com,
mail@johnericson.me,
"Christian Brauner (Amutable)" <brauner@kernel.org>
Subject: [PATCH 12/21] binfmt_misc: let a bpf handler run the interpreter transparently
Date: Mon, 20 Jul 2026 11:33:35 +0200 [thread overview]
Message-ID: <20260720-work-bpf-binfmt_misc-ptinterp-v1-12-ddb76c9a508e@kernel.org> (raw)
In-Reply-To: <20260720-work-bpf-binfmt_misc-ptinterp-v1-0-ddb76c9a508e@kernel.org>
Expose transparent mode 'T' to the bpf handler via a new
BPF_BINPRM_TRANSPARENT flag. A bpf handler can decide per binary whether
the dispatch is transparent. This way users may choose a native-looking
loader for one binary and a visible wrapper invocation for the next.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Documentation/admin-guide/binfmt-misc.rst | 14 ++++++++++++--
fs/binfmt_misc.c | 2 ++
fs/binfmt_misc_bpf.c | 13 ++++++++++---
include/linux/binfmt_misc.h | 4 ++++
4 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/Documentation/admin-guide/binfmt-misc.rst b/Documentation/admin-guide/binfmt-misc.rst
index c370c72c7dbe..b27ad31847ab 100644
--- a/Documentation/admin-guide/binfmt-misc.rst
+++ b/Documentation/admin-guide/binfmt-misc.rst
@@ -185,8 +185,8 @@ interpreter and the binary, exactly like the optional argument of a ``#!``
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``
-and ``O`` - are per-exec choices for a bpf handler, made by the ``load``
+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:
@@ -198,6 +198,16 @@ decide them differently for each binary it handles:
- ``BPF_BINPRM_EXECFD`` opens the binary on the interpreter's behalf and
passes it through the ``AT_EXECFD`` aux vector entry (the ``O`` flag), so
the interpreter can run binaries it could not open by path.
+- ``BPF_BINPRM_TRANSPARENT`` runs the interpreter transparently (the ``T``
+ flag): the binary is handed over through ``AT_EXECFD`` as
+ with ``BPF_BINPRM_EXECFD``, but the argument vector is also left as the
+ caller passed it. An interpreter that loads the binary from ``AT_EXECFD``
+ then appears in ``argv[0]`` and ``/proc/pid/cmdline`` as a direct
+ execution of the binary. ``BPF_BINPRM_PRESERVE_ARGV0`` is rejected in
+ combination with it, just as ``P`` is with ``T``. It also lets a handler
+ 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.
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.
diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c
index ed5ffb330749..1cd30dec3fab 100644
--- a/fs/binfmt_misc.c
+++ b/fs/binfmt_misc.c
@@ -350,6 +350,8 @@ static unsigned long entry_invocation_flags(const struct binfmt_misc_entry *e,
flags |= MISC_FMT_OPEN_BINARY;
if (bpf_flags & BPF_BINPRM_CREDENTIALS)
flags |= MISC_FMT_CREDENTIALS | MISC_FMT_OPEN_BINARY;
+ if (bpf_flags & BPF_BINPRM_TRANSPARENT)
+ flags |= MISC_FMT_TRANSPARENT | MISC_FMT_OPEN_BINARY;
return flags;
}
diff --git a/fs/binfmt_misc_bpf.c b/fs/binfmt_misc_bpf.c
index 00c787e8bdcc..d06cacc5c8c7 100644
--- a/fs/binfmt_misc_bpf.c
+++ b/fs/binfmt_misc_bpf.c
@@ -178,15 +178,22 @@ __bpf_kfunc int bpf_binprm_set_interp_arg(struct linux_binprm *bprm,
* O 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.
- * Calling it again replaces the flags, passing zero clears them again.
+ * 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.
*
- * Return: 0 on success, -EINVAL if @flags contains an unknown bit
+ * Return: 0 on success, -EINVAL if @flags contains an unknown bit or an
+ * invalid combination
*/
__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_EXECFD | BPF_BINPRM_TRANSPARENT))
+ return -EINVAL;
+
+ /* Transparency preserves the whole argv, argv[0] included. */
+ if ((flags & BPF_BINPRM_TRANSPARENT) && (flags & BPF_BINPRM_PRESERVE_ARGV0))
return -EINVAL;
bprm->bpf_flags = flags;
diff --git a/include/linux/binfmt_misc.h b/include/linux/binfmt_misc.h
index d3112a00cc19..26da749391b4 100644
--- a/include/linux/binfmt_misc.h
+++ b/include/linux/binfmt_misc.h
@@ -16,6 +16,9 @@ struct user_namespace;
* @BPF_BINPRM_CREDENTIALS: compute credentials from the binary; implies execfd
* (like the 'C' flag)
* @BPF_BINPRM_EXECFD: pass the binary via AT_EXECFD (like the 'O' flag)
+ * @BPF_BINPRM_TRANSPARENT: leave argv untouched, the interpreter takes the
+ * binary from AT_EXECFD (like the 'T' flag); implies
+ * execfd, excludes preserve-argv0
*
* 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.
@@ -24,6 +27,7 @@ enum bpf_binprm_flags {
BPF_BINPRM_PRESERVE_ARGV0 = (1ULL << 0),
BPF_BINPRM_CREDENTIALS = (1ULL << 1),
BPF_BINPRM_EXECFD = (1ULL << 2),
+ BPF_BINPRM_TRANSPARENT = (1ULL << 3),
};
/**
--
2.53.0
next prev parent reply other threads:[~2026-07-20 9:34 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-20 9:33 [PATCH 00/21] binfmt_misc: transparent interpreters and PT_INTERP loader substitution Christian Brauner
2026-07-20 9:33 ` [PATCH 01/21] exec: do not act on a stale execfd request without an executable Christian Brauner
2026-07-20 9:33 ` [PATCH 02/21] docs, binfmt_misc: keep general usage out of the handler sections Christian Brauner
2026-07-20 9:33 ` [PATCH 03/21] binfmt_misc: table-drive the register string flags Christian Brauner
2026-07-20 9:33 ` [PATCH 04/21] binfmt_misc: normalize the per-exec invocation flags Christian Brauner
2026-07-20 9:33 ` [PATCH 05/21] binfmt_misc: split out entry_open_interpreter() Christian Brauner
2026-07-20 9:33 ` [PATCH 06/21] binfmt_misc: split out build_interp_argv() Christian Brauner
2026-07-20 9:33 ` [PATCH 07/21] exec: release the replaced file with do_close_execat() Christian Brauner
2026-07-20 9:33 ` [PATCH 08/21] exec: add AT_FLAGS_TRANSPARENT_INTERP Christian Brauner
2026-07-20 9:33 ` [PATCH 09/21] exec: label mm->exe_file with the binary for a transparent dispatch Christian Brauner
2026-07-20 9:33 ` [PATCH 10/21] binfmt_misc: add transparent interpreter dispatch Christian Brauner
2026-07-20 9:33 ` [PATCH 11/21] binfmt_misc: add a static transparent flag 'T' Christian Brauner
2026-07-20 9:33 ` Christian Brauner [this message]
2026-07-20 9:33 ` [PATCH 13/21] selftests/exec: convert the binfmt_misc bpf test to the kselftest harness Christian Brauner
2026-07-20 9:33 ` [PATCH 14/21] selftests/exec: test the transparent binfmt_misc mode Christian Brauner
2026-07-20 9:33 ` [PATCH 15/21] binfmt_misc: document the transparent identity contract Christian Brauner
2026-07-20 9:33 ` [PATCH 16/21] exec: carry a PT_INTERP substitute in struct linux_binprm Christian Brauner
2026-07-20 9:33 ` [PATCH 17/21] binfmt_elf: consume a stashed PT_INTERP substitute Christian Brauner
2026-07-20 9:33 ` [PATCH 18/21] binfmt_misc: add the 'L' loader substitution flag Christian Brauner
2026-07-20 9:33 ` [PATCH 19/21] binfmt_misc: let a bpf handler request loader substitution Christian Brauner
2026-07-20 9:33 ` [PATCH 20/21] selftests/exec: test binfmt_misc " Christian Brauner
2026-07-20 9:33 ` [PATCH 21/21] binfmt_misc: document " Christian Brauner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260720-work-bpf-binfmt_misc-ptinterp-v1-12-ddb76c9a508e@kernel.org \
--to=brauner@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=corbet@lwn.net \
--cc=daniel@iogearbox.net \
--cc=farid.m.zakaria@gmail.com \
--cc=jack@suse.cz \
--cc=jannh@google.com \
--cc=kees@kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=mail@johnericson.me \
--cc=viro@zeniv.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox