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) --- 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