summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-17 08:35:25 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-17 08:35:25 -0700
commitb9cba7ebfe539f3e4bbdd03a1e0efa3b30b3f592 (patch)
treeaff2220d3b5e523e5c0c08c5e67eb471dc5f2f26
parent043d7a2b4045df5eebc699c39bc3a571afb9f1ae (diff)
parent68aabd01ddd26ced458a9e5716a640eaf8e4b7a6 (diff)
downloadlinux-b9cba7ebfe539f3e4bbdd03a1e0efa3b30b3f592.tar.gz
linux-b9cba7ebfe539f3e4bbdd03a1e0efa3b30b3f592.zip
Merge tag 'vfs-7.3-rc1.binfmt' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull binfmt updates from Christian Brauner: "This contains a bunch of work for binfmt_misc. It fixes a bunch of old bugs, reworks the locking, and then extends the format registry so a binary type can be matched programmatically and its interpreter computed per exec instead of being a fixed string recorded at registration time. This allows nixos and other to e.g., implement relocatable binaries meaning the interpreter/dynamic loader can be determined programatically, say found relative to the binary. The mechanism is flexible and can support other policies: - Handler lookup is now an rcu walk. An exec that matches no binfmt_misc entry should now never write to a shared cacheline - remove the VERBOSE_STATUS and USE_DEBUG compile time toggles - convert the entry file to a seq_file which simplifies things quite a bit and kills a lot of custom logic - make flags proper enums - rename struct Node to binfmt_misc_entry - allow entries to be removed with unlink(2) - Add the ability to attach bpf programs to binfmt_misc entries so it's possible to dynamically choose the execution environment such as the loader or interpreter on a per binary basis. A handler is an instance of a binfmt_misc_ops struct_ops with a ->match() and a ->load() program. match() decides from the entry lookup walk whether the handler applies under the same registration-order. It can read file content as needed not only the prefetched 256 bytes in bprm->buf. load() then selects the interpreter and stages it through the new bpf_binprm_set_interp(), bpf_binprm_set_interp_arg() and bpf_binprm_set_flags() kfuncs. Handlers are published in a registry keyed by the registering task's user namespace and activated through the existing text interface with a new 'B' type carrying the handler name: echo ':origin:B::::nix:' > /proc/sys/fs/binfmt_misc/register The permission and namespacing model is unchanged. Activating a handler requires the same write access to an instance as any other registration. A container mounting its own instance escapes the host's entries exactly as before. The computed interpreter is opened with open_exec() under the caller's credentials and goes through full LSM vetting as the next binprm level. A program can only ever redirect the caller to something the caller could exec anyway. - Two dispatch modes are added. So far the chosen interpreter owns the whole process identity (argv[0], /proc/pid/cmdline, /proc/self/exe all name interpreter information). So relocatable find the dynamic linker instead. Also a binary passed to execveat() as an inaccessible O_CLOEXEC fd cannot run at all and gdb trips because AT_ENTRY and AT_PHDR do not match the exe file. So PIE symbols are unrelocated. This adds transparent dispatch which allows the interpreter to load the binary through AT_EXECFD and leaves the argument vector exactly as the caller built it and labels mm->exe_file and comm with the binary. It also raises the AT_FLAGS_TRANSPARENT_INTERP aux vector bit. The interpreter keeps control of mapping the binary. The second mode is loader substitution. This allows a binary to be executed natively and only the interpreter to be changed. - Last, interpreters can be bound at registration time. Each interpreter is opened by its own write with the credentials the entry file was opened with. The program picks one per exec with bpf_binprm_select_interp(). Ucounts are used to properly account for pre-opened interpreters via /proc/sys/user/max_binfmt_misc_interpreters" * tag 'vfs-7.3-rc1.binfmt' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: (63 commits) binfmt_misc: document the pre-opened interpreter limit selftests/exec: test the pre-opened interpreter limit binfmt_misc: correctly account pre-opened interpreters binfmt_misc: document interpreters bound by a 'B' entry selftests/exec: test interpreters bound to a 'B' entry binfmt_misc: let a 'B' entry bind its interpreters binfmt_misc: carry pre-opened interpreters in struct binfmt_misc_interp selftests/exec: share the bpf handler preconditions binfmt_misc: document registering an entry disabled selftests/exec: test registering an entry disabled selftests/exec: let binfmt_flag_supported() return a bool selftests/exec: check that a binfmt_misc instance cannot be pinned binfmt_misc: let a register string create an entry disabled binfmt_misc: document loader substitution selftests/exec: test binfmt_misc loader substitution binfmt_misc: let a bpf handler request loader substitution binfmt_misc: add the 'L' loader substitution flag binfmt_elf_fdpic: consume a stashed PT_INTERP substitute binfmt_elf: consume a stashed PT_INTERP substitute exec: carry a PT_INTERP substitute in struct linux_binprm ...
-rw-r--r--Documentation/admin-guide/binfmt-misc.rst246
-rw-r--r--fs/Kconfig.binfmt14
-rw-r--r--fs/Makefile1
-rw-r--r--fs/binfmt_elf.c10
-rw-r--r--fs/binfmt_elf_fdpic.c11
-rw-r--r--fs/binfmt_misc.c1628
-rw-r--r--fs/binfmt_misc_bpf.c434
-rw-r--r--fs/bpf_fs_kfuncs.c28
-rw-r--r--fs/exec.c88
-rw-r--r--include/linux/binfmt_misc.h113
-rw-r--r--include/linux/binfmts.h40
-rw-r--r--include/linux/user_namespace.h3
-rw-r--r--include/uapi/linux/binfmts.h7
-rw-r--r--kernel/ucount.c6
-rw-r--r--kernel/user.c4
-rw-r--r--tools/testing/selftests/exec/.gitignore11
-rw-r--r--tools/testing/selftests/exec/Makefile93
-rw-r--r--tools/testing/selftests/exec/binfmt_bind_interp.c14
-rw-r--r--tools/testing/selftests/exec/binfmt_bpf_app.c12
-rw-r--r--tools/testing/selftests/exec/binfmt_bpf_interp.c15
-rw-r--r--tools/testing/selftests/exec/binfmt_loader_payload.c146
-rw-r--r--tools/testing/selftests/exec/binfmt_misc_bpf.c638
-rw-r--r--tools/testing/selftests/exec/binfmt_misc_common.h315
-rw-r--r--tools/testing/selftests/exec/binfmt_misc_disabled.c172
-rw-r--r--tools/testing/selftests/exec/binfmt_misc_interplimit.c232
-rw-r--r--tools/testing/selftests/exec/binfmt_misc_loader.c372
-rw-r--r--tools/testing/selftests/exec/binfmt_misc_selfpin.c158
-rw-r--r--tools/testing/selftests/exec/binfmt_misc_transparent.c95
-rw-r--r--tools/testing/selftests/exec/binfmt_transparent_interp.c112
-rw-r--r--tools/testing/selftests/exec/bpf_interp.bpf.c61
-rw-r--r--tools/testing/selftests/exec/config10
-rw-r--r--tools/testing/selftests/exec/interp_bind.bpf.c76
-rw-r--r--tools/testing/selftests/exec/loader.bpf.c56
-rw-r--r--tools/testing/selftests/exec/nix_origin.bpf.c224
-rw-r--r--tools/testing/selftests/exec/transparent.bpf.c57
35 files changed, 4974 insertions, 528 deletions
diff --git a/Documentation/admin-guide/binfmt-misc.rst b/Documentation/admin-guide/binfmt-misc.rst
index c0a34fbf8022..d26b63a27c25 100644
--- a/Documentation/admin-guide/binfmt-misc.rst
+++ b/Documentation/admin-guide/binfmt-misc.rst
@@ -26,7 +26,8 @@ Here is what the fields mean:
name below ``/proc/sys/fs/binfmt_misc``; cannot contain slashes ``/`` for
obvious reasons.
- ``type``
- is the type of recognition. Give ``M`` for magic and ``E`` for extension.
+ is the type of recognition. Give ``M`` for magic, ``E`` for extension and
+ ``B`` for a bpf-backed handler (see below).
- ``offset``
is the offset of the magic/mask in the file, counted in bytes. This
defaults to 0 if you omit it (i.e. you write ``:name:type::magic...``).
@@ -48,7 +49,8 @@ Here is what the fields mean:
filename extension matching.
- ``interpreter``
is the program that should be invoked with the binary as first
- argument (specify the full path)
+ argument (specify the full path). For ``B`` entries this field
+ carries the name of the bpf handler instead (see below).
- ``flags``
is an optional field that controls several aspects of the invocation
of the interpreter. It is a string of capital letters, each controls a
@@ -88,6 +90,32 @@ Here is what the fields mean:
emulation is installed and uses the opened image to spawn the
emulator, meaning it is always available once installed,
regardless of how the environment changes.
+ ``T`` - transparent
+ Run the interpreter transparently. The binary is handed to
+ the interpreter through ``AT_EXECFD`` (``T`` implies ``O``),
+ the argument vector is left exactly as the caller built it
+ and the kernel labels ``/proc/pid/exe`` with the binary
+ instead of the interpreter. The interpreter has to load the
+ binary from ``AT_EXECFD`` and follow the
+ ``AT_FLAGS_TRANSPARENT_INTERP`` contract. Combining ``T``
+ with ``P`` is rejected: transparency preserves the whole
+ argument vector, argv[0] included.
+ ``L`` - loader substitution
+ Do not run the interpreter on the binary at all: load the
+ binary itself as a fully native exec and substitute the
+ interpreter for the loader named in the binary's
+ ``PT_INTERP``. See the "Loader substitution" section
+ below. ``L`` rejects ``T``, ``P``, ``O`` and ``C``;
+ ``F`` composes.
+ ``D`` - registered disabled
+ The entry is created disabled instead of being matchable at
+ once, and has to be enabled by writing ``1`` to its file
+ before it dispatches anything. This splits a registration
+ into creating the entry and activating it, leaving room to
+ configure it in between - which is what a ``B`` entry that
+ binds interpreters needs; see the bpf section below. The flag
+ is spent on the registration and is not read back: what an
+ entry file reports afterwards is whether it is enabled.
There are some restrictions:
@@ -96,6 +124,16 @@ There are some restrictions:
- the magic must reside in the first 128 bytes of the file, i.e.
offset+size(magic) has to be less than 128
- the interpreter string may not exceed 127 characters
+ - an interpreter used with ``C`` or ``L`` but without ``F`` has to be
+ named by an absolute path. It is opened when the binary is executed, so
+ a relative one would be resolved against the working directory of
+ whoever runs the binary
+ - the amount of pre-opened interpreters by ``F``, or bound to a ``B`` entry
+ is limited by the ``/proc/sys/user/max_binfmt_misc_interpreters`` sysctl. A
+ registration past the limit is refused with ``-ENOSPC``. This limits an
+ unprivileged namespace pinning files. A nested namespace can raise only its
+ own limit and every ancestor is charged too
+
To use binfmt_misc you have to mount it first. You can mount it with
``mount -t binfmt_misc none /proc/sys/fs/binfmt_misc`` command, or you can add
@@ -133,7 +171,209 @@ or 1 (to enable) to ``/proc/sys/fs/binfmt_misc/status`` or
Catting the file tells you the current status of ``binfmt_misc/the_entry``.
You can remove one entry or all entries by echoing -1 to ``/proc/.../the_name``
-or ``/proc/sys/fs/binfmt_misc/status``.
+or ``/proc/sys/fs/binfmt_misc/status``. A single entry can also be removed
+by simply unlinking (``rm``) ``/proc/.../the_name``.
+
+
+bpf-backed handlers
+-------------------
+
+With ``CONFIG_BINFMT_MISC_BPF`` both the matching and the interpreter
+selection can be delegated to bpf programs. A handler is an instance of the
+``binfmt_misc_ops`` struct_ops with a ``match`` and a ``load`` program and a
+``name``. Once the struct_ops map is registered the handler can be activated
+with a ``B`` entry that references it by name in the ``interpreter`` field
+and carries neither offset, magic, nor mask::
+
+ echo ':qemu:B::::my_handler:' > register
+
+Both programs receive the ``linux_binprm`` of the binary and both can
+sleep. The ``match`` program decides whether the handler applies: it is
+consulted during the entry walk exactly like magic and extension matching,
+in the same registration order with the same first-match-wins semantics.
+Unlike static matching it is not limited to the prefetched first bytes of
+the file in ``bprm->buf``: it can read the file, e.g. to parse ELF program
+headers whose data sits at arbitrary offsets. It only decides, though: the
+selection kfuncs below are rejected in it. The ``load`` program of the
+matched handler then selects the interpreter: it can equally read the file
+and derive the interpreter from the binary's location. It selects the
+interpreter by calling the ``bpf_binprm_set_interp()`` kfunc with an
+absolute path and returning ``0``. A match is committed: a failing
+``load`` fails the exec with its error instead of falling through to later
+entries; ``-ENOEXEC`` lets the remaining binary formats have a go. A path
+selected this way is opened with the credentials of the task doing the
+exec, exactly as a statically registered interpreter without ``F`` would
+be.
+
+An entry can instead bind the interpreters its handler may use, so that no
+path is resolved at exec time at all. An entry registered with ``D`` is not
+matchable yet, which is what leaves it open to being given them, one
+``+name path`` write at a time::
+
+ echo ':qemu:B::::my_handler:D' > register
+ echo '+aarch64 /usr/bin/qemu-aarch64' > qemu
+ echo '+arm /usr/bin/qemu-arm' > qemu
+ echo 1 > qemu
+
+Each path is opened during its write, in the writing process's context and
+with the credentials the entry file was opened with, exactly the way ``F``
+pre-opens a static entry's interpreter; the paths must be absolute. The
+path is everything past the first space, so there is nothing it cannot
+express, and no interpreter has to fit in a register string. An entry
+binds at most 100 interpreters, and each one is charged against
+``max_binfmt_misc_interpreters`` like any other binding. A write past either
+limit is refused with ``-ENOSPC``.
+
+The ``load`` program then selects one per exec by name with the
+``bpf_binprm_select_interp()`` kfunc, and every exec runs a clone of the
+file that was opened. The path decides which file is bound and nothing
+else: it is not resolved again, in any namespace, so what it holds later -
+or what it holds in the namespace of whoever runs the binary - no longer
+decides anything.
+
+Enabling the entry ends this. Its interpreters are read at exec time with
+nothing but a reference held on the entry, so an entry that has ever been
+matchable can never have its set changed again: the first ``1`` seals it,
+from then on ``+`` is refused with ``-EBUSY``, and an entry registered
+without ``D`` is sealed from the start. Binding a name twice is refused
+with ``-EEXIST``.
+
+Selection is by name so that the configuration and the program need not
+agree on an order, and so that a handler is not tied to where a distribution
+puts its interpreters. A name is a single word of printable ASCII, at most
+32 characters; a name the entry did not bind gives the program ``-ENOENT``,
+which it can act on or return. The interpreter runs under the path it was
+registered under, and the entry reports what it bound::
+
+ $ cat /proc/sys/fs/binfmt_misc/qemu
+ enabled
+ bpf my_handler
+ bpf-interpreter aarch64 /usr/bin/qemu-aarch64
+ bpf-interpreter arm /usr/bin/qemu-arm
+ flags:
+
+The path reported is the one the interpreter was bound under, which named
+the file at that moment; it is not re-resolved, so it is a record of what
+was bound rather than a promise about what that path holds now.
+
+The ``load`` program can also pass a single argument to the interpreter with
+the ``bpf_binprm_set_interp_arg()`` kfunc. It is inserted between the
+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``,
+``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).
+- ``BPF_BINPRM_CREDENTIALS`` computes credentials from the binary (the ``C``
+ flag), bounded to user namespaces that map the binary's owner just like
+ any other setuid exec.
+- ``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`` and a staged
+ interpreter argument are 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.
+- ``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 invocation
+flags in the register string; ``F`` has none to spell for it either, since
+the interpreters it binds already pre-open what ``F`` would. The
+registration directive ``D`` is the exception: it decides how the entry
+starts out, not how the interpreter is invoked.
+
+A handler is looked up only in the user namespace the struct_ops map was
+registered in. Handlers are not inherited, so an entry can only reference a
+handler registered in the same user namespace as its binfmt_misc instance.
+The entry keeps the handler alive; deleting the struct_ops map only prevents
+new activations.
+
+
+Transparent interpreters
+------------------------
+
+With the ``T`` flag or ``BPF_BINPRM_TRANSPARENT`` the dispatch is invisible
+to the resulting process. The argument vector is left exactly as the caller
+built it. The binary is passed through ``AT_EXECFD``. The kernel also labels
+``/proc/pid/exe`` correctly. The binary's file is write-denied while the
+process runs and the interpreter's is not, exactly as if the binary had been
+executed directly. A transparent entry does not change how credentials are
+derived. As
+with any other entry, set*id bits of the binary are only honored with ``C`` (or
+``BPF_BINPRM_CREDENTIALS``).
+
+The interpreter has to be built for this contract. The kernel announces it
+with ``AT_FLAGS_TRANSPARENT_INTERP`` in the ``AT_FLAGS`` aux vector entry
+next to ``AT_EXECFD``. The argument vector belongs entirely to the program,
+nothing was spliced in, so the interpreter doesn't consume arguments and
+simply loads the program from the descriptor. The bit is also the loader's
+license to finish the identity. After mapping the program it may retarget the
+``AT_PHDR``/``AT_ENTRY``/``AT_BASE`` entries of ``/proc/pid/auxv`` and the
+code/data statistics markers via one ``PR_SET_MM_MAP`` which completes
+what attaching debuggers observe. What remains visibly different from a
+direct execution is the address space layout. The interpreter occupies
+the main-image position and the program lives in the mmap region.
+
+
+Loader substitution
+-------------------
+
+The ``L`` flag turns the execution model around. Instead of running the
+registered interpreter with the binary as its payload the kernel loads
+the matched binary itself as the main image and substitutes the registered
+interpreter for the loader named in the binary's ``PT_INTERP``.
+
+Because the exec is native, there is no dispatch identity to
+reconstruct and no contract the substitute has to implement. A stock
+dynamic loader works unchanged. The argument vector is untouched,
+credentials and ``AT_SECURE`` derive from the binary, there is no
+``AT_EXECFD`` and no marker in the aux vector, the binary sits in the
+main-image slot with the native brk placement so ``/proc/pid/maps``,
+core dumps and perf mmap records have the native shape, and the
+identity is already complete when ``PTRACE_EVENT_EXEC`` stops the
+tracee. So launching under a debugger works, not just attaching. ``L``
+entries are for ELF binaries of a native architecture. Foreign-arch
+emulation and non-ELF payloads remain the domain of the classic and
+transparent modes.
+
+The override applies when the format that finally claims the file is
+ELF with a ``PT_INTERP``. A matched binary without one or an
+interpreter-less ``ET_DYN`` drops the override and runs natively. A file
+claimed by another format - a ``#!`` script, say - is handled by that
+format as if the entry had not matched. ``L`` is therefore not an
+enforcement mechanism: it decides how a binary that asks for a loader is
+run, it does not guarantee that everything matching the entry runs under
+the substitute. A format that cannot consume the override at all instead
+refuses the exec with ``ENOEXEC`` before the point of no return.
+
+A wrong-architecture ELF fails the whole exec with ``ENOEXEC`` exactly
+as if no entry had matched. A substitute that is not ELF of the right
+architecture fails with ``ELIBBAD``. The usual ``PT_INTERP`` sanity
+checks on the binary still apply. But the segment's content is otherwise
+irrelevant.
+
+``L`` rejects the classic-dispatch flags ``T``, ``P``, ``O`` and ``C``
+at registration. ``F`` composes and is valuable: with it the substitute
+is opened at registration time, so later mount namespace or path changes
+cannot redirect it. Without it the substitute is opened when the binary
+is executed, and the path is resolved in the mount namespace and root of
+whoever runs the binary, which is why it has to be absolute. As with
+``C``, register only trusted interpreters. The substituted loader runs
+with credentials derived from the binary.
Hints
diff --git a/fs/Kconfig.binfmt b/fs/Kconfig.binfmt
index 1949e25c7741..daeac4889d03 100644
--- a/fs/Kconfig.binfmt
+++ b/fs/Kconfig.binfmt
@@ -168,6 +168,20 @@ config BINFMT_MISC
you have use for it; the module is called binfmt_misc. If you
don't know what to answer at this point, say Y.
+config BINFMT_MISC_BPF
+ bool "BPF-selected interpreters for misc binaries"
+ depends on BINFMT_MISC=y
+ depends on BPF_SYSCALL && BPF_JIT && DEBUG_INFO_BTF
+ help
+ Allow binfmt_misc binary type handlers to be implemented as bpf
+ struct_ops programs. Instead of matching a fixed magic and
+ redirecting to a fixed interpreter recorded at registration time
+ such handlers match binaries programmatically and compute the
+ interpreter to use per binary, e.g. relative to the location of
+ the binary itself.
+
+ If you don't know what to answer at this point, say N.
+
config COREDUMP
bool "Enable core dump support" if EXPERT
default y
diff --git a/fs/Makefile b/fs/Makefile
index 89a8a9d207d1..499c6670f0c1 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_FS_ENCRYPTION) += crypto/
obj-$(CONFIG_FS_VERITY) += verity/
obj-$(CONFIG_FILE_LOCKING) += locks.o
obj-$(CONFIG_BINFMT_MISC) += binfmt_misc.o
+obj-$(CONFIG_BINFMT_MISC_BPF) += binfmt_misc_bpf.o
obj-$(CONFIG_BINFMT_SCRIPT) += binfmt_script.o
obj-$(CONFIG_BINFMT_ELF) += binfmt_elf.o
obj-$(CONFIG_COMPAT_BINFMT_ELF) += compat_binfmt_elf.o
diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c
index 16a56b6b3f6c..00ff35cad441 100644
--- a/fs/binfmt_elf.c
+++ b/fs/binfmt_elf.c
@@ -179,7 +179,6 @@ create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec,
unsigned char k_rand_bytes[16];
int items;
elf_addr_t *elf_info;
- elf_addr_t flags = 0;
int ei_index;
const struct cred *cred = current_cred();
struct vm_area_struct *vma;
@@ -254,9 +253,7 @@ create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec,
NEW_AUX_ENT(AT_PHENT, sizeof(struct elf_phdr));
NEW_AUX_ENT(AT_PHNUM, exec->e_phnum);
NEW_AUX_ENT(AT_BASE, interp_load_addr);
- if (bprm->interp_flags & BINPRM_FLAGS_PRESERVE_ARGV0)
- flags |= AT_FLAGS_PRESERVE_ARGV0;
- NEW_AUX_ENT(AT_FLAGS, flags);
+ NEW_AUX_ENT(AT_FLAGS, bprm_at_flags(bprm));
NEW_AUX_ENT(AT_ENTRY, e_entry);
NEW_AUX_ENT(AT_UID, from_kuid_munged(cred->user_ns, cred->uid));
NEW_AUX_ENT(AT_EUID, from_kuid_munged(cred->user_ns, cred->euid));
@@ -904,7 +901,7 @@ static int load_elf_binary(struct linux_binprm *bprm)
if (elf_interpreter[elf_ppnt->p_filesz - 1] != '\0')
goto out_free_interp;
- interpreter = open_exec(elf_interpreter);
+ interpreter = bprm_open_interpreter(bprm, elf_interpreter);
kfree(elf_interpreter);
retval = PTR_ERR(interpreter);
if (IS_ERR(interpreter))
@@ -935,6 +932,9 @@ out_free_interp:
goto out_free_ph;
}
+ /* No PT_INTERP to substitute for: the override does not apply. */
+ bprm_drop_loader(bprm);
+
elf_ppnt = elf_phdata;
for (i = 0; i < elf_ex->e_phnum; i++, elf_ppnt++)
switch (elf_ppnt->p_type) {
diff --git a/fs/binfmt_elf_fdpic.c b/fs/binfmt_elf_fdpic.c
index fe0b5c5ed2bc..068c46875c74 100644
--- a/fs/binfmt_elf_fdpic.c
+++ b/fs/binfmt_elf_fdpic.c
@@ -263,7 +263,8 @@ static int load_elf_fdpic_binary(struct linux_binprm *bprm)
kdebug("Using ELF interpreter %s", interpreter_name);
/* replace the program with the interpreter */
- interpreter = open_exec(interpreter_name);
+ interpreter = bprm_open_interpreter(bprm,
+ interpreter_name);
retval = PTR_ERR(interpreter);
if (IS_ERR(interpreter)) {
interpreter = NULL;
@@ -299,6 +300,9 @@ static int load_elf_fdpic_binary(struct linux_binprm *bprm)
}
+ /* No PT_INTERP to substitute for: the override does not apply. */
+ bprm_drop_loader(bprm);
+
if (is_constdisp(&exec_params.hdr))
exec_params.flags |= ELF_FDPIC_FLAG_CONSTDISP;
@@ -509,7 +513,6 @@ static int create_elf_fdpic_tables(struct linux_binprm *bprm,
char *k_platform, *k_base_platform;
char __user *u_platform, *u_base_platform, *p;
int loop;
- unsigned long flags = 0;
int ei_index;
elf_addr_t *elf_info;
@@ -649,9 +652,7 @@ static int create_elf_fdpic_tables(struct linux_binprm *bprm,
NEW_AUX_ENT(AT_PHENT, sizeof(struct elf_phdr));
NEW_AUX_ENT(AT_PHNUM, exec_params->hdr.e_phnum);
NEW_AUX_ENT(AT_BASE, interp_params->elfhdr_addr);
- if (bprm->interp_flags & BINPRM_FLAGS_PRESERVE_ARGV0)
- flags |= AT_FLAGS_PRESERVE_ARGV0;
- NEW_AUX_ENT(AT_FLAGS, flags);
+ NEW_AUX_ENT(AT_FLAGS, bprm_at_flags(bprm));
NEW_AUX_ENT(AT_ENTRY, exec_params->entry_addr);
NEW_AUX_ENT(AT_UID, (elf_addr_t) from_kuid_munged(cred->user_ns, cred->uid));
NEW_AUX_ENT(AT_EUID, (elf_addr_t) from_kuid_munged(cred->user_ns, cred->euid));
diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c
index 613dd28e3f1a..ddfd3aa57ac8 100644
--- a/fs/binfmt_misc.c
+++ b/fs/binfmt_misc.c
@@ -10,45 +10,98 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/hex.h>
-#include <linux/init.h>
-#include <linux/sched/mm.h>
-#include <linux/magic.h>
+#include <linux/array_size.h>
+#include <linux/binfmt_misc.h>
#include <linux/binfmts.h>
-#include <linux/slab.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/bug.h>
+#include <linux/cleanup.h>
+#include <linux/cred.h>
#include <linux/ctype.h>
-#include <linux/string_helpers.h>
#include <linux/file.h>
-#include <linux/pagemap.h>
-#include <linux/namei.h>
-#include <linux/mount.h>
-#include <linux/fs_context.h>
-#include <linux/syscalls.h>
#include <linux/fs.h>
+#include <linux/fs_context.h>
+#include <linux/init.h>
+#include <linux/kstrtox.h>
+#include <linux/limits.h>
+#include <linux/list.h>
+#include <linux/magic.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/rculist.h>
+#include <linux/refcount.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/srcu.h>
+#include <linux/string.h>
+#include <linux/string_helpers.h>
#include <linux/uaccess.h>
+#include <linux/user_namespace.h>
#include "internal.h"
-#ifdef DEBUG
-# define USE_DEBUG 1
-#else
-# define USE_DEBUG 0
-#endif
+/* Entry status and match type bit numbers. */
+enum binfmt_misc_entry_bits {
+ MISC_FMT_ENABLED_BIT = 0,
+ MISC_FMT_MAGIC_BIT = 1,
+ MISC_FMT_BPF_BIT = 2,
+};
+
+/* Entry behavior flags, fixed at registration time. */
+enum binfmt_misc_entry_flags {
+ MISC_FMT_PRESERVE_ARGV0 = (1U << 31),
+ MISC_FMT_OPEN_BINARY = (1U << 30),
+ MISC_FMT_CREDENTIALS = (1U << 29),
+ MISC_FMT_OPEN_FILE = (1U << 28),
+ MISC_FMT_TRANSPARENT = (1U << 27),
+ MISC_FMT_LOADER = (1U << 26),
+ MISC_FMT_DISABLED = (1U << 25),
+};
-enum {
- VERBOSE_STATUS = 1 /* make it zero to save 400 bytes kernel memory */
+/* The flags that shape the invocation; a 'B' handler picks those per exec. */
+#define MISC_FMT_INVOCATION_FLAGS (MISC_FMT_PRESERVE_ARGV0 | \
+ MISC_FMT_OPEN_BINARY | \
+ MISC_FMT_CREDENTIALS | \
+ MISC_FMT_OPEN_FILE | \
+ MISC_FMT_TRANSPARENT | \
+ MISC_FMT_LOADER)
+
+/**
+ * struct binfmt_misc_flag - a flag character of the register string
+ * @c: the character userspace writes and reads back
+ * @flag: the entry flag it sets
+ * @implies: entry flags it turns on in addition
+ * @desc: what it does, for the registration debug output
+ */
+struct binfmt_misc_flag {
+ char c;
+ unsigned long flag;
+ unsigned long implies;
+ const char *desc;
};
-enum {Enabled, Magic};
-#define MISC_FMT_PRESERVE_ARGV0 (1UL << 31)
-#define MISC_FMT_OPEN_BINARY (1UL << 30)
-#define MISC_FMT_CREDENTIALS (1UL << 29)
-#define MISC_FMT_OPEN_FILE (1UL << 28)
+static const struct binfmt_misc_flag misc_flags[] = {
+ { 'P', MISC_FMT_PRESERVE_ARGV0, 0, "preserve argv0" },
+ { 'O', MISC_FMT_OPEN_BINARY, 0, "open binary" },
+ { 'C', MISC_FMT_CREDENTIALS, MISC_FMT_OPEN_BINARY, "credentials from the binary" },
+ { 'F', MISC_FMT_OPEN_FILE, 0, "open interpreter file now" },
+ { 'T', MISC_FMT_TRANSPARENT, MISC_FMT_OPEN_BINARY, "transparent" },
+ { 'L', MISC_FMT_LOADER, 0, "loader substitution" },
+ { 'D', MISC_FMT_DISABLED, 0, "register disabled" },
+};
+
+/* Look up a flag character, NULL if @c is not one. */
+static const struct binfmt_misc_flag *misc_flag_by_char(const char c)
+{
+ for (int i = 0; i < ARRAY_SIZE(misc_flags); i++)
+ if (misc_flags[i].c == c)
+ return &misc_flags[i];
+ return NULL;
+}
-typedef struct {
- struct list_head list;
+struct binfmt_misc_entry {
+ struct hlist_node node;
unsigned long flags; /* type, status, etc. */
int offset; /* offset of magic */
int size; /* size of magic/mask */
@@ -57,11 +110,13 @@ typedef struct {
const char *interpreter; /* filename of interpreter */
char *name;
struct dentry *dentry;
- struct file *interp_file;
+ const struct binfmt_misc_ops *bpf_ops; /* bpf-backed handler ('B') */
+ const char *bpf_ops_name;
+ struct list_head interps; /* the interpreters it bound */
refcount_t users; /* sync removal with load_misc_binary() */
-} Node;
-
-static struct file_system_type bm_fs_type;
+ struct rcu_head rcu;
+ char buf[]; /* register string, fields point in here */
+};
/*
* Max length of the register string. Determined by:
@@ -74,54 +129,88 @@ static struct file_system_type bm_fs_type;
* - interp: ~50 bytes
* - flags: 5 bytes
* Round that up a bit, and then back off to hold the internal data
- * (like struct Node).
+ * (like struct binfmt_misc_entry).
*/
#define MAX_REGISTER_LENGTH 1920
+/* Trailing delimiter pad so field parsing always terminates at a delimiter. */
+#define MISC_DELIM_PAD 8
+
+/* Protects the entry walk in load_misc_binary(), which may sleep in it. */
+DEFINE_STATIC_SRCU_FAST(bm_entries_srcu);
+
+/* Check if @e's magic matches @bprm's buffer, applying the mask if set. */
+static bool entry_matches_magic(const struct binfmt_misc_entry *e,
+ const struct linux_binprm *bprm)
+{
+ const char *s = bprm->buf + e->offset;
+ int i;
+
+ if (!e->mask)
+ return !memcmp(s, e->magic, e->size);
+
+ for (i = 0; i < e->size; i++)
+ if ((s[i] ^ e->magic[i]) & e->mask[i])
+ return false;
+ return true;
+}
+
+/* Check if @e's registered extension matches @ext, NULL if there is none. */
+static bool entry_matches_extension(const struct binfmt_misc_entry *e,
+ const char *ext)
+{
+ return ext && !strcmp(e->magic, ext);
+}
+
/**
* search_binfmt_handler - search for a binary handler for @bprm
* @misc: handle to binfmt_misc instance
* @bprm: binary for which we are looking for a handler
*
* Search for a binary type handler for @bprm in the list of registered binary
- * type handlers.
+ * type handlers. A 'B' entry's match program decides whether the handler
+ * applies; it may sleep to read the binary. The matched entry is returned
+ * with a reference taken while the walk still held it; a dying entry -
+ * unlinked with its last reference gone - cannot be matched and the walk
+ * moves on.
*
- * Return: binary type list entry on success, NULL on failure
+ * The caller must hold the bm_entries_srcu read lock, which allows an
+ * entry's evaluation to sleep.
+ *
+ * Return: referenced binary type list entry on success, NULL on failure
*/
-static Node *search_binfmt_handler(struct binfmt_misc *misc,
- struct linux_binprm *bprm)
+static struct binfmt_misc_entry *
+search_binfmt_handler(struct binfmt_misc *misc, struct linux_binprm *bprm)
{
- char *p = strrchr(bprm->interp, '.');
- Node *e;
+ char *dot = strrchr(bprm->interp, '.');
+ const char *ext = dot ? dot + 1 : NULL;
+ struct binfmt_misc_entry *e;
/* Walk all the registered handlers. */
- list_for_each_entry(e, &misc->entries, list) {
- char *s;
- int j;
-
- /* Make sure this one is currently enabled. */
- if (!test_bit(Enabled, &e->flags))
- continue;
-
- /* Do matching based on extension if applicable. */
- if (!test_bit(Magic, &e->flags)) {
- if (p && !strcmp(e->magic, p + 1))
- return e;
+ hlist_for_each_entry_rcu(e, &misc->entries, node,
+ srcu_read_lock_held(&bm_entries_srcu)) {
+ /*
+ * Make sure this one is currently enabled. An entry enters
+ * the list at most once and only whole: its configuration is
+ * ordered before the rcu insertion that makes it visible
+ * here.
+ */
+ if (!test_bit(MISC_FMT_ENABLED_BIT, &e->flags))
continue;
- }
- /* Do matching based on magic & mask. */
- s = bprm->buf + e->offset;
- if (e->mask) {
- for (j = 0; j < e->size; j++)
- if ((*s++ ^ e->magic[j]) & e->mask[j])
- break;
+ if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) {
+ if (!e->bpf_ops->match(bprm))
+ continue;
+ } else if (test_bit(MISC_FMT_MAGIC_BIT, &e->flags)) {
+ if (!entry_matches_magic(e, bprm))
+ continue;
} else {
- for (j = 0; j < e->size; j++)
- if ((*s++ ^ e->magic[j]))
- break;
+ if (!entry_matches_extension(e, ext))
+ continue;
}
- if (j == e->size)
+
+ /* A dying entry cannot be matched, walk on. */
+ if (refcount_inc_not_zero(&e->users))
return e;
}
@@ -133,165 +222,483 @@ static Node *search_binfmt_handler(struct binfmt_misc *misc,
* @misc: handle to binfmt_misc instance
* @bprm: binary for which we are looking for a handler
*
- * Try to find a binfmt handler for the binary type. If one is found take a
- * reference to protect against removal via bm_{entry,status}_write().
+ * Try to find a binfmt handler for the binary type. If one is found it is
+ * returned with a reference protecting it against removal via
+ * bm_{entry,status}_write().
*
* Return: binary type list entry on success, NULL on failure
*/
-static Node *get_binfmt_handler(struct binfmt_misc *misc,
- struct linux_binprm *bprm)
+static struct binfmt_misc_entry *get_binfmt_handler(struct binfmt_misc *misc,
+ struct linux_binprm *bprm)
{
- Node *e;
-
- read_lock(&misc->entries_lock);
- e = search_binfmt_handler(misc, bprm);
- if (e)
- refcount_inc(&e->users);
- read_unlock(&misc->entries_lock);
- return e;
+ guard(srcu_fast)(&bm_entries_srcu);
+ return search_binfmt_handler(misc, bprm);
}
/**
- * put_binfmt_handler - put binary handler node
- * @e: node to put
+ * binfmt_misc_find_interp - find a bound interpreter by name
+ * @interps: the interpreters the matched entry was registered with
+ * @name: the name to look for
*
- * Free node syncing with load_misc_binary() and defer final free to
+ * Return: the interpreter on success, NULL if @interps has none by that name
+ */
+const struct binfmt_misc_interp *
+binfmt_misc_find_interp(const struct list_head *interps, const char *name)
+{
+ struct binfmt_misc_interp *interp;
+
+ list_for_each_entry(interp, interps, list)
+ if (!strcmp(interp->name, name))
+ return interp;
+ return NULL;
+}
+
+/* Undo the open_exec() a pre-opened interpreter file came from. */
+static void close_interp_file(struct file *f)
+{
+ if (IS_ERR_OR_NULL(f))
+ return;
+ exe_file_allow_write_access(f);
+ filp_close(f, NULL);
+}
+
+DEFINE_FREE(close_interp_file, struct file *, close_interp_file(_T))
+
+/*
+ * Open an interpreter @path for execution: now, in the writer's context,
+ * and - since binfmt_misc mounts can be unprivileged - with @cred, the
+ * credentials the control file being written was opened with, not the
+ * writer's own.
+ */
+static struct file *open_interp_file(const struct cred *cred, const char *path)
+{
+ struct file *f;
+
+ scoped_with_creds(cred)
+ f = open_exec(path);
+ if (IS_ERR(f))
+ pr_notice("register: failed to install interpreter %s\n", path);
+ return f;
+}
+
+/* Release the interpreters an entry was registered with. */
+static void entry_put_interpreters(struct binfmt_misc_entry *e)
+{
+ struct binfmt_misc_interp *interp, *tmp;
+
+ list_for_each_entry_safe(interp, tmp, &e->interps, list) {
+ list_del(&interp->list);
+ close_interp_file(interp->file);
+ dec_ucount(interp->ucounts, UCOUNT_BINFMT_MISC_INTERPRETERS);
+ kfree(interp);
+ }
+}
+
+/**
+ * entry_attach_interpreter - bind an opened interpreter to @e
+ * @e: entry being configured
+ * @name: name the load program will select it by; empty for the fixed
+ * interpreter of a static entry
+ * @path: the path @f was opened from
+ * @f: the interpreter, opened for execution
+ *
+ * Every exec runs a clone of @f, so the path decided which file is bound
+ * and nothing else: it is not resolved again, in any namespace.
+ *
+ * The caller has to have validated @name and @path, established that @e
+ * cannot be matched yet, and owns @f until this succeeds.
+ *
+ * Return: 0 on success, -ENOSPC if the entry is full or the binder is out of
+ * UCOUNT_BINFMT_MISC_INTERPRETERS budget, a negative errno on failure
+ */
+static int entry_attach_interpreter(struct binfmt_misc_entry *e,
+ const char *name, const char *path,
+ struct file *f)
+{
+ size_t nlen = strlen(name), plen = strlen(path);
+ struct binfmt_misc_interp *interp;
+ struct ucounts *ucounts;
+
+ if (binfmt_misc_find_interp(&e->interps, name))
+ return -EEXIST;
+ if (list_count_nodes(&e->interps) >= BINFMT_MISC_INTERP_MAX)
+ return -ENOSPC;
+
+ /* The binding keeps a file open, so charge it to whoever binds it. */
+ ucounts = inc_ucount(current_user_ns(), current_euid(),
+ UCOUNT_BINFMT_MISC_INTERPRETERS);
+ if (!ucounts)
+ return -ENOSPC;
+
+ /* One allocation, both strings in it, like the entry's own buffer. */
+ interp = kmalloc(struct_size(interp, name, nlen + plen + 2),
+ GFP_KERNEL_ACCOUNT);
+ if (!interp) {
+ dec_ucount(ucounts, UCOUNT_BINFMT_MISC_INTERPRETERS);
+ return -ENOMEM;
+ }
+
+ interp->path = interp->name + nlen + 1;
+ strscpy(interp->name, name, nlen + 1);
+ strscpy(interp->name + nlen + 1, path, plen + 1);
+ interp->file = f;
+ interp->ucounts = ucounts;
+ /* Publish the node: a lockless cat may be walking the list. */
+ list_add_tail_rcu(&interp->list, &e->interps);
+ pr_debug("register: interpreter: %s {%s}\n", name, path);
+ return 0;
+}
+
+static void bm_entry_free_rcu(struct rcu_head *rcu)
+{
+ struct binfmt_misc_entry *e = container_of(rcu, struct binfmt_misc_entry, rcu);
+
+ /* No walker that could sleep in the handler's programs is left. */
+ if (e->bpf_ops)
+ binfmt_misc_put_ops(e->bpf_ops);
+ kfree(e);
+}
+
+/**
+ * put_binfmt_handler - put binary handler entry
+ * @e: entry to put
+ *
+ * Free entry syncing with load_misc_binary() and defer final free to
* load_misc_binary() in case it is using the binary type handler we were
- * requested to remove.
+ * requested to remove. Also the teardown for a registration that fails
+ * before add_entry() publishes the entry.
*/
-static void put_binfmt_handler(Node *e)
+static void put_binfmt_handler(struct binfmt_misc_entry *e)
{
+ if (IS_ERR_OR_NULL(e))
+ return;
+
if (refcount_dec_and_test(&e->users)) {
- if (e->flags & MISC_FMT_OPEN_FILE) {
- exe_file_allow_write_access(e->interp_file);
- filp_close(e->interp_file, NULL);
- }
- kfree(e);
+ entry_put_interpreters(e);
+ /* Walkers may still dereference this entry, even sleeping. */
+ call_srcu(&bm_entries_srcu, &e->rcu, bm_entry_free_rcu);
+ }
+}
+
+DEFINE_FREE(put_binfmt_handler, struct binfmt_misc_entry *, put_binfmt_handler(_T))
+
+/* Drop everything a load program staged for this exec. */
+static void drop_staged_selection(struct linux_binprm *bprm)
+{
+ kfree(bprm->bpf_interp);
+ bprm->bpf_interp = NULL;
+ kfree(bprm->bpf_interp_arg);
+ bprm->bpf_interp_arg = NULL;
+ if (bprm->bpf_interp_file) {
+ fput(bprm->bpf_interp_file);
+ bprm->bpf_interp_file = NULL;
}
+ bprm->bpf_flags = 0;
}
/**
- * load_binfmt_misc - load the binfmt_misc of the caller's user namespace
+ * current_binfmt_misc - get the binfmt_misc instance of the caller's user namespace
*
- * To be called in load_misc_binary() to load the relevant struct binfmt_misc.
- * If a user namespace doesn't have its own binfmt_misc mount it can make use
- * of its ancestor's binfmt_misc handlers. This mimicks the behavior of
- * pre-namespaced binfmt_misc where all registered binfmt_misc handlers where
- * available to all user and user namespaces on the system.
+ * If a user namespace doesn't have its own binfmt_misc mount it uses the
+ * handlers of its closest ancestor with one. This mimics the behavior of
+ * pre-namespaced binfmt_misc where all registered handlers were available
+ * to all users and user namespaces on the system. The init user namespace
+ * instance is statically set up so the fallback is never reached in
+ * practice.
*
* Return: the binfmt_misc instance of the caller's user namespace
*/
-static struct binfmt_misc *load_binfmt_misc(void)
+static struct binfmt_misc *current_binfmt_misc(void)
{
const struct user_namespace *user_ns;
struct binfmt_misc *misc;
- user_ns = current_user_ns();
- while (user_ns) {
+ for (user_ns = current_user_ns(); user_ns; user_ns = user_ns->parent) {
/* Pairs with smp_store_release() in bm_fill_super(). */
misc = smp_load_acquire(&user_ns->binfmt_misc);
if (misc)
return misc;
-
- user_ns = user_ns->parent;
}
return &init_binfmt_misc;
}
-/*
- * the loader itself
+/**
+ * entry_select_interpreter - get the interpreter for the matched @e
+ * @e: matched binary type handler
+ * @bprm: binary that is being executed
+ *
+ * A static entry carries its interpreter path, for a 'B' entry the
+ * handler's load program selects it, either by path or by the name of one
+ * of the interpreters the entry bound. The match is committed, so a failing
+ * program fails the exec.
+ *
+ * Return: the interpreter on success, an ERR_PTR on failure
*/
-static int load_misc_binary(struct linux_binprm *bprm)
+static const char *entry_select_interpreter(const struct binfmt_misc_entry *e,
+ struct linux_binprm *bprm)
{
- Node *fmt;
- struct file *interp_file = NULL;
- int retval = -ENOEXEC;
- struct binfmt_misc *misc;
+ int retval;
- misc = load_binfmt_misc();
- if (!misc->enabled)
- return retval;
+ /*
+ * Drop what a previous chain level staged before anything can pick it
+ * up. A static entry stages nothing but consumes a staged file just
+ * like a 'B' entry does.
+ */
+ drop_staged_selection(bprm);
+
+ if (!test_bit(MISC_FMT_BPF_BIT, &e->flags))
+ return e->interpreter;
+
+ /* The interpreters this entry lets the program choose from. */
+ bprm->bpf_interps = &e->interps;
+ retval = e->bpf_ops->load(bprm);
+ bprm->bpf_interps = NULL;
+ if (retval) {
+ /* Keep a program-supplied error within errno range. */
+ if (retval > 0 || retval < -MAX_ERRNO)
+ retval = -ENOEXEC;
+ goto drop_staged;
+ }
- fmt = get_binfmt_handler(misc, bprm);
- if (!fmt)
- return retval;
+ /* Selecting an interpreter is part of the contract. */
+ if (!bprm->bpf_interp) {
+ retval = -ENOEXEC;
+ goto drop_staged;
+ }
+
+ return bprm->bpf_interp;
+
+drop_staged:
+ /* A failing load leaves nothing behind for later entries. */
+ drop_staged_selection(bprm);
+ return ERR_PTR(retval);
+}
- /* Need to be able to load the file after exec */
- retval = -ENOENT;
+/**
+ * entry_invocation_flags - the invocation flags in effect for this exec
+ * @e: matched binary type handler
+ * @bprm: binary that is being executed
+ *
+ * A static entry fixes its flags at registration, a 'B' entry's load program
+ * picks them per exec with bpf_binprm_set_flags(). Translate the latter into
+ * the former, implications included, so the dispatch has one set to act on.
+ *
+ * Return: the invocation flags for this exec
+ */
+static unsigned long entry_invocation_flags(const struct binfmt_misc_entry *e,
+ struct linux_binprm *bprm)
+{
+ unsigned long flags = 0;
+ u64 bpf_flags;
+
+ if (!test_bit(MISC_FMT_BPF_BIT, &e->flags))
+ return e->flags;
+
+ bpf_flags = bprm->bpf_flags;
+ /* Clear so they can't accumulate into a nested interpreter level. */
+ bprm->bpf_flags = 0;
+
+ if (bpf_flags & BPF_BINPRM_PRESERVE_ARGV0)
+ flags |= MISC_FMT_PRESERVE_ARGV0;
+ if (bpf_flags & BPF_BINPRM_EXECFD)
+ 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;
+ if (bpf_flags & BPF_BINPRM_LOADER)
+ flags |= MISC_FMT_LOADER;
+
+ return flags;
+}
+
+/**
+ * entry_open_interpreter - open the entry's interpreter for execution
+ * @e: matched binary type handler
+ * @bprm: binary that is being executed
+ * @interpreter: the interpreter selected for this exec
+ *
+ * An 'F' entry hands out a clone of the file it pre-opened at registration,
+ * and so does a 'B' entry whose load program selected one of the
+ * interpreters it bound. Any other entry opens the selected path.
+ *
+ * Return: the opened interpreter on success, an ERR_PTR on failure
+ */
+static struct file *entry_open_interpreter(const struct binfmt_misc_entry *e,
+ struct linux_binprm *bprm,
+ const char *interpreter)
+{
+ struct file *interp_file __free(fput) = NULL;
+ struct binfmt_misc_interp *interp;
+ struct file *bound;
+ int retval;
+
+ if (bprm->bpf_interp_file) {
+ bound = bprm->bpf_interp_file;
+ } else if (e->flags & MISC_FMT_OPEN_FILE) {
+ /* An 'F' entry pre-opened exactly one interpreter. */
+ interp = list_first_entry(&e->interps,
+ struct binfmt_misc_interp, list);
+ bound = interp->file;
+ } else {
+ return open_exec(interpreter);
+ }
+
+ interp_file = file_clone_open(bound);
+ if (IS_ERR(interp_file))
+ return interp_file;
+
+ retval = exe_file_deny_write_access(interp_file);
+ if (retval)
+ return ERR_PTR(retval);
+
+ return no_free_ptr(interp_file);
+}
+
+/**
+ * build_interp_argv - splice the interpreter invocation into the argv
+ * @bprm: binary that is being executed
+ * @interpreter: the interpreter selected for this exec
+ * @flags: invocation flags in effect for this exec
+ *
+ * The interpreter becomes argv[0] and the binary its last argument, with an
+ * optional staged argument in between. The caller's argv[0] is dropped
+ * unless 'P' keeps it.
+ *
+ * Return: 0 on success, a negative error code on failure
+ */
+static int build_interp_argv(struct linux_binprm *bprm, const char *interpreter,
+ unsigned long flags)
+{
+ int retval;
+
+ /* The interpreter has to be able to load the binary by path. */
if (bprm->interp_flags & BINPRM_FLAGS_PATH_INACCESSIBLE)
- goto ret;
+ return -ENOENT;
- if (fmt->flags & MISC_FMT_PRESERVE_ARGV0) {
+ /* The entry's own choice - not one accumulated from an earlier level. */
+ if (flags & MISC_FMT_PRESERVE_ARGV0) {
bprm->interp_flags |= BINPRM_FLAGS_PRESERVE_ARGV0;
} else {
retval = remove_arg_zero(bprm);
if (retval)
- goto ret;
+ return retval;
}
- /* make argv[1] be the path to the binary */
+ /* make the binary the last argument to the interpreter */
retval = copy_string_kernel(bprm->interp, bprm);
if (retval < 0)
- goto ret;
+ return retval;
bprm->argc++;
+ /*
+ * A single optional argument to the interpreter, inserted between it
+ * and the binary just like the argument of a #! interpreter line.
+ */
+ if (bprm->bpf_interp_arg) {
+ retval = copy_string_kernel(bprm->bpf_interp_arg, bprm);
+ if (retval < 0)
+ return retval;
+ bprm->argc++;
+ /* Consumed - don't let it leak into a nested interpreter's argv. */
+ kfree(bprm->bpf_interp_arg);
+ bprm->bpf_interp_arg = NULL;
+ }
+
/* add the interp as argv[0] */
- retval = copy_string_kernel(fmt->interpreter, bprm);
+ retval = copy_string_kernel(interpreter, bprm);
if (retval < 0)
- goto ret;
+ return retval;
bprm->argc++;
- /* Update interp in case binfmt_script needs it. */
- retval = bprm_change_interp(fmt->interpreter, bprm);
- if (retval < 0)
- goto ret;
+ return 0;
+}
- if (fmt->flags & MISC_FMT_OPEN_FILE) {
- interp_file = file_clone_open(fmt->interp_file);
- if (!IS_ERR(interp_file)) {
- int err = exe_file_deny_write_access(interp_file);
+/*
+ * the loader itself
+ */
+static int load_misc_binary(struct linux_binprm *bprm)
+{
+ struct binfmt_misc_entry *fmt __free(put_binfmt_handler) = NULL;
+ const char *interpreter;
+ struct file *interp_file;
+ struct binfmt_misc *misc;
+ unsigned long flags;
+ int retval;
+
+ /* Only binfmt_misc stages one and exec_binprm() clears it per round. */
+ WARN_ON_ONCE(bprm->loader);
+
+ misc = current_binfmt_misc();
+ if (!READ_ONCE(misc->enabled))
+ return -ENOEXEC;
- if (err) {
- fput(interp_file);
- interp_file = ERR_PTR(err);
- }
+ fmt = get_binfmt_handler(misc, bprm);
+ if (!fmt)
+ return -ENOEXEC;
+
+ interpreter = entry_select_interpreter(fmt, bprm);
+ if (IS_ERR(interpreter))
+ return PTR_ERR(interpreter);
+
+ flags = entry_invocation_flags(fmt, bprm);
+
+ /* No argv is built for a staged argument to land in. */
+ if ((flags & (MISC_FMT_LOADER | MISC_FMT_TRANSPARENT)) &&
+ bprm->bpf_interp_arg)
+ return -EINVAL;
+
+ /*
+ * Stash the interpreter for binfmt_elf to consume in place of the
+ * binary's PT_INTERP and decline the match, so the search continues
+ * to the real format in the same round.
+ */
+ if (flags & MISC_FMT_LOADER) {
+ interp_file = entry_open_interpreter(fmt, bprm, interpreter);
+ if (IS_ERR(interp_file)) {
+ retval = PTR_ERR(interp_file);
+ /* Declining here would run the binary's own PT_INTERP. */
+ return retval == -ENOEXEC ? -EACCES : retval;
}
- } else {
- interp_file = open_exec(fmt->interpreter);
+
+ bprm->loader = interp_file;
+ return -ENOEXEC;
+ }
+
+ if (!(flags & MISC_FMT_TRANSPARENT)) {
+ retval = build_interp_argv(bprm, interpreter, flags);
+ if (retval)
+ return retval;
}
- retval = PTR_ERR(interp_file);
+
+ /* Update interp for the next round; sched_prepare_exec reports it. */
+ retval = bprm_change_interp(interpreter, bprm);
+ if (retval < 0)
+ return retval;
+
+ interp_file = entry_open_interpreter(fmt, bprm, interpreter);
if (IS_ERR(interp_file))
- goto ret;
+ return PTR_ERR(interp_file);
+
+ /* Raise only past the last failure, or an -ENOEXEC decline leaks it. */
+ if (flags & MISC_FMT_TRANSPARENT)
+ bprm->interp_flags |= BINPRM_FLAGS_TRANSPARENT_INTERP;
bprm->interpreter = interp_file;
- if (fmt->flags & MISC_FMT_OPEN_BINARY)
+ if (flags & MISC_FMT_OPEN_BINARY)
bprm->have_execfd = 1;
- if (fmt->flags & MISC_FMT_CREDENTIALS)
+ if (flags & MISC_FMT_CREDENTIALS)
bprm->execfd_creds = 1;
-
- retval = 0;
-ret:
-
- /*
- * If we actually put the node here all concurrent calls to
- * load_misc_binary() will have finished. We also know
- * that for the refcount to be zero someone must have concurently
- * removed the binary type handler from the list and it's our job to
- * free it.
- */
- put_binfmt_handler(fmt);
-
- return retval;
+ return 0;
}
/* Command parsers */
/*
- * parses and copies one argument enclosed in del from *sp to *dp,
- * recognising the \x special.
- * returns pointer to the copied argument or NULL in case of an
- * error (and sets err) or null argument length.
+ * Scan the argument starting at @s up to the delimiter @del, recognising
+ * the \x escape. Terminates the argument with a NUL and returns a pointer
+ * past it or NULL on a malformed escape.
*/
static char *scanarg(char *s, char del)
{
@@ -306,46 +713,130 @@ static char *scanarg(char *s, char del)
return NULL;
}
}
- s[-1] ='\0';
+ s[-1] = '\0';
return s;
}
-static char *check_special_flags(char *sfs, Node *e)
+/* Parse the 'flags' field, stopping at the first character that is not one. */
+static char *check_special_flags(char *p, struct binfmt_misc_entry *e)
{
- char *p = sfs;
- int cont = 1;
-
- /* special flags */
- while (cont) {
- switch (*p) {
- case 'P':
- pr_debug("register: flag: P (preserve argv0)\n");
- p++;
- e->flags |= MISC_FMT_PRESERVE_ARGV0;
- break;
- case 'O':
- pr_debug("register: flag: O (open binary)\n");
- p++;
- e->flags |= MISC_FMT_OPEN_BINARY;
- break;
- case 'C':
- pr_debug("register: flag: C (preserve creds)\n");
- p++;
- /* this flags also implies the
- open-binary flag */
- e->flags |= (MISC_FMT_CREDENTIALS |
- MISC_FMT_OPEN_BINARY);
- break;
- case 'F':
- pr_debug("register: flag: F: open interpreter file now\n");
- p++;
- e->flags |= MISC_FMT_OPEN_FILE;
- break;
- default:
- cont = 0;
- }
+ for (;; p++) {
+ const struct binfmt_misc_flag *f = misc_flag_by_char(*p);
+
+ if (!f)
+ return p;
+ pr_debug("register: flag: %c (%s)\n", f->c, f->desc);
+ e->flags |= f->flag | f->implies;
+ }
+}
+
+/* Parse the 'offset', 'magic' and 'mask' fields of an 'M' entry. */
+static char *parse_magic_fields(struct binfmt_misc_entry *e, char *p, char del)
+{
+ char *s;
+
+ /* Parse the 'offset' field. */
+ s = strchr(p, del);
+ if (!s)
+ return NULL;
+ *s = '\0';
+ if (p != s) {
+ if (kstrtoint(p, 10, &e->offset) || e->offset < 0)
+ return NULL;
+ }
+ p = s + 1;
+ pr_debug("register: offset: %#x\n", e->offset);
+
+ /* Parse the 'magic' field. */
+ e->magic = p;
+ p = scanarg(p, del);
+ if (!p || !e->magic[0])
+ return NULL;
+ print_hex_dump_debug(
+ KBUILD_MODNAME ": register: magic[raw]: ",
+ DUMP_PREFIX_NONE, 16, 1, e->magic, p - e->magic, true);
+
+ /* Parse the 'mask' field. */
+ e->mask = p;
+ p = scanarg(p, del);
+ if (!p)
+ return NULL;
+ if (!e->mask[0]) {
+ e->mask = NULL;
+ pr_debug("register: mask[raw]: none\n");
+ } else {
+ print_hex_dump_debug(
+ KBUILD_MODNAME ": register: mask[raw]: ",
+ DUMP_PREFIX_NONE, 16, 1, e->mask, p - e->mask, true);
}
+ /*
+ * Decode the magic & mask fields. Note: while we might have accepted
+ * embedded NUL bytes from above, the unescape helpers will stop at
+ * the first one they encounter.
+ */
+ e->size = string_unescape_inplace(e->magic, UNESCAPE_HEX);
+ if (e->mask && string_unescape_inplace(e->mask, UNESCAPE_HEX) != e->size)
+ return NULL;
+ if (e->size > BINPRM_BUF_SIZE || BINPRM_BUF_SIZE - e->size < e->offset)
+ return NULL;
+ pr_debug("register: magic/mask length: %i\n", e->size);
+ print_hex_dump_debug(
+ KBUILD_MODNAME ": register: magic[decoded]: ",
+ DUMP_PREFIX_NONE, 16, 1, e->magic, e->size, true);
+ if (e->mask)
+ print_hex_dump_debug(
+ KBUILD_MODNAME ": register: mask[decoded]: ",
+ DUMP_PREFIX_NONE, 16, 1, e->mask, e->size, true);
+ return p;
+}
+
+/* Parse the 'magic' field of an 'E' entry: the filename extension. */
+static char *parse_extension_fields(struct binfmt_misc_entry *e, char *p,
+ char del)
+{
+ /* Skip the 'offset' field. */
+ p = strchr(p, del);
+ if (!p)
+ return NULL;
+ *p++ = '\0';
+
+ /* Parse the 'magic' field. */
+ e->magic = p;
+ p = strchr(p, del);
+ if (!p)
+ return NULL;
+ *p++ = '\0';
+ if (!e->magic[0] || strchr(e->magic, '/'))
+ return NULL;
+ pr_debug("register: extension: {%s}\n", e->magic);
+
+ /* Skip the 'mask' field. */
+ p = strchr(p, del);
+ if (!p)
+ return NULL;
+ *p++ = '\0';
+ return p;
+}
+
+/*
+ * Parse the fields of a 'B' entry: the 'offset', 'magic' and 'mask' fields
+ * must be empty. The handler name is carried in the 'interpreter' field.
+ */
+static char *parse_bpf_fields(struct binfmt_misc_entry *e, char *p, char del)
+{
+ /* The 'offset' field must be empty. */
+ if (*p++ != del)
+ return NULL;
+
+ /* The 'magic' field must be empty. */
+ if (*p++ != del)
+ return NULL;
+
+ /* The 'mask' field must be empty. */
+ if (*p++ != del)
+ return NULL;
+
return p;
}
@@ -354,54 +845,53 @@ static char *check_special_flags(char *sfs, Node *e)
* ':name:type:offset:magic:mask:interpreter:flags'
* where the ':' is the IFS, that can be chosen with the first char
*/
-static Node *create_entry(const char __user *buffer, size_t count)
+static struct binfmt_misc_entry *create_entry(const char __user *buffer,
+ size_t count)
{
- Node *e;
- int memsize, err;
+ struct binfmt_misc_entry *e __free(kfree) = NULL;
char *buf, *p;
char del;
pr_debug("register: received %zu bytes\n", count);
/* some sanity checks */
- err = -EINVAL;
if ((count < 11) || (count > MAX_REGISTER_LENGTH))
- goto out;
+ return ERR_PTR(-EINVAL);
- err = -ENOMEM;
- memsize = sizeof(Node) + count + 8;
- e = kmalloc(memsize, GFP_KERNEL_ACCOUNT);
+ e = kmalloc(struct_size(e, buf, count + MISC_DELIM_PAD),
+ GFP_KERNEL_ACCOUNT);
if (!e)
- goto out;
+ return ERR_PTR(-ENOMEM);
- p = buf = (char *)e + sizeof(Node);
+ p = buf = e->buf;
- memset(e, 0, sizeof(Node));
+ memset(e, 0, sizeof(*e));
+ INIT_LIST_HEAD(&e->interps);
if (copy_from_user(buf, buffer, count))
- goto efault;
+ return ERR_PTR(-EFAULT);
- del = *p++; /* delimeter */
+ del = *p++; /* delimiter */
pr_debug("register: delim: %#x {%c}\n", del, del);
/* A flag-char delimiter runs the flag scan off the buffer. */
- if (del == 'P' || del == 'O' || del == 'C' || del == 'F')
- goto einval;
+ if (misc_flag_by_char(del))
+ return ERR_PTR(-EINVAL);
/* Pad the buffer with the delim to simplify parsing below. */
- memset(buf + count, del, 8);
+ memset(buf + count, del, MISC_DELIM_PAD);
/* Parse the 'name' field. */
e->name = p;
p = strchr(p, del);
if (!p)
- goto einval;
+ return ERR_PTR(-EINVAL);
*p++ = '\0';
if (!e->name[0] ||
!strcmp(e->name, ".") ||
!strcmp(e->name, "..") ||
strchr(e->name, '/'))
- goto einval;
+ return ERR_PTR(-EINVAL);
pr_debug("register: name: {%s}\n", e->name);
@@ -409,225 +899,213 @@ static Node *create_entry(const char __user *buffer, size_t count)
switch (*p++) {
case 'E':
pr_debug("register: type: E (extension)\n");
- e->flags = 1 << Enabled;
+ e->flags = BIT(MISC_FMT_ENABLED_BIT);
break;
case 'M':
pr_debug("register: type: M (magic)\n");
- e->flags = (1 << Enabled) | (1 << Magic);
+ e->flags = BIT(MISC_FMT_ENABLED_BIT) | BIT(MISC_FMT_MAGIC_BIT);
+ break;
+ case 'B':
+ pr_debug("register: type: B (bpf)\n");
+ if (!IS_ENABLED(CONFIG_BINFMT_MISC_BPF))
+ return ERR_PTR(-EINVAL);
+ e->flags = BIT(MISC_FMT_ENABLED_BIT) | BIT(MISC_FMT_BPF_BIT);
break;
default:
- goto einval;
+ return ERR_PTR(-EINVAL);
}
if (*p++ != del)
- goto einval;
-
- if (test_bit(Magic, &e->flags)) {
- /* Handle the 'M' (magic) format. */
- char *s;
-
- /* Parse the 'offset' field. */
- s = strchr(p, del);
- if (!s)
- goto einval;
- *s = '\0';
- if (p != s) {
- int r = kstrtoint(p, 10, &e->offset);
- if (r != 0 || e->offset < 0)
- goto einval;
- }
- p = s;
- if (*p++)
- goto einval;
- pr_debug("register: offset: %#x\n", e->offset);
-
- /* Parse the 'magic' field. */
- e->magic = p;
- p = scanarg(p, del);
- if (!p)
- goto einval;
- if (!e->magic[0])
- goto einval;
- if (USE_DEBUG)
- print_hex_dump_bytes(
- KBUILD_MODNAME ": register: magic[raw]: ",
- DUMP_PREFIX_NONE, e->magic, p - e->magic);
-
- /* Parse the 'mask' field. */
- e->mask = p;
- p = scanarg(p, del);
- if (!p)
- goto einval;
- if (!e->mask[0]) {
- e->mask = NULL;
- pr_debug("register: mask[raw]: none\n");
- } else if (USE_DEBUG)
- print_hex_dump_bytes(
- KBUILD_MODNAME ": register: mask[raw]: ",
- DUMP_PREFIX_NONE, e->mask, p - e->mask);
-
- /*
- * Decode the magic & mask fields.
- * Note: while we might have accepted embedded NUL bytes from
- * above, the unescape helpers here will stop at the first one
- * it encounters.
- */
- e->size = string_unescape_inplace(e->magic, UNESCAPE_HEX);
- if (e->mask &&
- string_unescape_inplace(e->mask, UNESCAPE_HEX) != e->size)
- goto einval;
- if (e->size > BINPRM_BUF_SIZE ||
- BINPRM_BUF_SIZE - e->size < e->offset)
- goto einval;
- pr_debug("register: magic/mask length: %i\n", e->size);
- if (USE_DEBUG) {
- print_hex_dump_bytes(
- KBUILD_MODNAME ": register: magic[decoded]: ",
- DUMP_PREFIX_NONE, e->magic, e->size);
-
- if (e->mask) {
- int i;
- char *masked = kmalloc(e->size, GFP_KERNEL_ACCOUNT);
-
- print_hex_dump_bytes(
- KBUILD_MODNAME ": register: mask[decoded]: ",
- DUMP_PREFIX_NONE, e->mask, e->size);
-
- if (masked) {
- for (i = 0; i < e->size; ++i)
- masked[i] = e->magic[i] & e->mask[i];
- print_hex_dump_bytes(
- KBUILD_MODNAME ": register: magic[masked]: ",
- DUMP_PREFIX_NONE, masked, e->size);
-
- kfree(masked);
- }
- }
- }
- } else {
- /* Handle the 'E' (extension) format. */
-
- /* Skip the 'offset' field. */
- p = strchr(p, del);
- if (!p)
- goto einval;
- *p++ = '\0';
-
- /* Parse the 'magic' field. */
- e->magic = p;
- p = strchr(p, del);
- if (!p)
- goto einval;
- *p++ = '\0';
- if (!e->magic[0] || strchr(e->magic, '/'))
- goto einval;
- pr_debug("register: extension: {%s}\n", e->magic);
-
- /* Skip the 'mask' field. */
- p = strchr(p, del);
- if (!p)
- goto einval;
- *p++ = '\0';
- }
+ return ERR_PTR(-EINVAL);
+
+ if (test_bit(MISC_FMT_BPF_BIT, &e->flags))
+ p = parse_bpf_fields(e, p, del);
+ else if (test_bit(MISC_FMT_MAGIC_BIT, &e->flags))
+ p = parse_magic_fields(e, p, del);
+ else
+ p = parse_extension_fields(e, p, del);
+ if (!p)
+ return ERR_PTR(-EINVAL);
/* Parse the 'interpreter' field. */
e->interpreter = p;
p = strchr(p, del);
if (!p)
- goto einval;
+ return ERR_PTR(-EINVAL);
*p++ = '\0';
- if (!e->interpreter[0])
- goto einval;
- pr_debug("register: interpreter: {%s}\n", e->interpreter);
+ if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) {
+ /* The 'interpreter' field carries the handler name. */
+ e->bpf_ops_name = e->interpreter;
+ e->interpreter = NULL;
+ if (!e->bpf_ops_name[0])
+ return ERR_PTR(-EINVAL);
+ pr_debug("register: bpf handler: {%s}\n", e->bpf_ops_name);
+ } else if (!e->interpreter[0]) {
+ return ERR_PTR(-EINVAL);
+ } else {
+ pr_debug("register: interpreter: {%s}\n", e->interpreter);
+ }
/* Parse the 'flags' field. */
p = check_special_flags(p, e);
+
+ /*
+ * A bpf handler decides the invocation flags per exec with
+ * bpf_binprm_set_flags() rather than fixing them at registration, and
+ * the interpreters it binds pre-open what 'F' would have, so a 'B'
+ * entry carries no invocation flags.
+ */
+ if (test_bit(MISC_FMT_BPF_BIT, &e->flags) &&
+ (e->flags & MISC_FMT_INVOCATION_FLAGS))
+ return ERR_PTR(-EINVAL);
+
+ /*
+ * 'D' is a directive for this registration rather than a lasting
+ * property, so consume it: the entry is created disabled and stays
+ * out of the search list until '1' is written to its entry file.
+ * Staying out is what leaves it open to being given interpreters;
+ * the first enable publishes it, for good.
+ */
+ if (e->flags & MISC_FMT_DISABLED) {
+ e->flags &= ~MISC_FMT_DISABLED;
+ clear_bit(MISC_FMT_ENABLED_BIT, &e->flags);
+ }
+
+ /* Transparency preserves the whole argv, argv[0] included. */
+ if ((e->flags & MISC_FMT_TRANSPARENT) &&
+ (e->flags & MISC_FMT_PRESERVE_ARGV0))
+ return ERR_PTR(-EINVAL);
+
+ /* A native exec splices no argv, passes no execfd and needs no creds. */
+ if ((e->flags & MISC_FMT_LOADER) &&
+ (e->flags & (MISC_FMT_TRANSPARENT | MISC_FMT_PRESERVE_ARGV0 |
+ MISC_FMT_CREDENTIALS | MISC_FMT_OPEN_BINARY)))
+ return ERR_PTR(-EINVAL);
+
if (*p == '\n')
p++;
if (p != buf + count)
- goto einval;
-
- return e;
+ return ERR_PTR(-EINVAL);
-out:
- return ERR_PTR(err);
+ /* Non-F opens the interp at exec against the caller's cwd; require absolute. */
+ if ((e->flags & (MISC_FMT_LOADER | MISC_FMT_CREDENTIALS)) &&
+ !(e->flags & MISC_FMT_OPEN_FILE) &&
+ e->interpreter[0] != '/')
+ return ERR_PTR(-EINVAL);
-efault:
- kfree(e);
- return ERR_PTR(-EFAULT);
-einval:
- kfree(e);
- return ERR_PTR(-EINVAL);
+ /* Born holding one reference; put_binfmt_handler() is the teardown. */
+ refcount_set(&e->users, 1);
+ return no_free_ptr(e);
}
+/* Commands accepted by the /status and /<entry> files. */
+enum bm_command {
+ BM_CMD_IGNORE, /* empty write */
+ BM_CMD_DISABLE, /* "0" */
+ BM_CMD_ENABLE, /* "1" */
+ BM_CMD_REMOVE, /* "-1" */
+};
+
+/* Longest of the commands above, "-1\n". */
+#define MAX_COMMAND_LENGTH 3
+
/*
- * Set status of entry/binfmt_misc:
- * '1' enables, '0' disables and '-1' clears entry/binfmt_misc
+ * Parse what userspace wrote to /status or an entry file: '1' enables,
+ * '0' disables and '-1' removes the entry or all entries.
*/
-static int parse_command(const char __user *buffer, size_t count)
+static int parse_command(const char *s, size_t count)
{
- char s[4];
-
- if (count > 3)
+ if (count > MAX_COMMAND_LENGTH)
return -EINVAL;
- if (copy_from_user(s, buffer, count))
- return -EFAULT;
if (!count)
- return 0;
+ return BM_CMD_IGNORE;
if (s[count - 1] == '\n')
count--;
if (count == 1 && s[0] == '0')
- return 1;
+ return BM_CMD_DISABLE;
if (count == 1 && s[0] == '1')
- return 2;
+ return BM_CMD_ENABLE;
if (count == 2 && s[0] == '-' && s[1] == '1')
- return 3;
+ return BM_CMD_REMOVE;
return -EINVAL;
}
+/* Copy in a command from a file that takes nothing else, and parse it. */
+static int read_command(const char __user *buffer, size_t count)
+{
+ char s[MAX_COMMAND_LENGTH + 1];
+
+ if (count > sizeof(s) - 1)
+ return -EINVAL;
+ if (copy_from_user(s, buffer, count))
+ return -EFAULT;
+ return parse_command(s, count);
+}
+
/* generic stuff */
-static void entry_status(Node *e, char *page)
+/* The root directory's inode; its lock serializes configuring an instance. */
+static struct inode *bm_root_inode(struct super_block *sb)
+{
+ return d_inode(sb->s_root);
+}
+
+static void bm_seq_hex(struct seq_file *m, const u8 *data, int size)
{
- char *dp = page;
- const char *status = "disabled";
+ for (int i = 0; i < size; i++)
+ seq_printf(m, "%02x", data[i]);
+}
- if (test_bit(Enabled, &e->flags))
- status = "enabled";
+static int bm_entry_show(struct seq_file *m, void *unused)
+{
+ struct binfmt_misc_entry *e = m->private;
- if (!VERBOSE_STATUS) {
- sprintf(page, "%s\n", status);
- return;
- }
+ if (test_bit(MISC_FMT_ENABLED_BIT, &e->flags))
+ seq_puts(m, "enabled\n");
+ else
+ seq_puts(m, "disabled\n");
- dp += sprintf(dp, "%s\ninterpreter %s\n", status, e->interpreter);
+ if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) {
+ struct binfmt_misc_interp *interp;
+
+ seq_printf(m, "bpf %s\n", e->bpf_ops->name);
+ /*
+ * A staged entry's set can still grow, so every binding is
+ * rcu-published. The open file pins the entry and with it
+ * every node, so rcu is for the tearing, not the lifetime.
+ */
+ rcu_read_lock();
+ list_for_each_entry_rcu(interp, &e->interps, list)
+ seq_printf(m, "bpf-interpreter %s %s\n",
+ interp->name, interp->path);
+ rcu_read_unlock();
+ } else {
+ seq_printf(m, "interpreter %s\n", e->interpreter);
+ }
/* print the special flags */
- dp += sprintf(dp, "flags: ");
- if (e->flags & MISC_FMT_PRESERVE_ARGV0)
- *dp++ = 'P';
- if (e->flags & MISC_FMT_OPEN_BINARY)
- *dp++ = 'O';
- if (e->flags & MISC_FMT_CREDENTIALS)
- *dp++ = 'C';
- if (e->flags & MISC_FMT_OPEN_FILE)
- *dp++ = 'F';
- *dp++ = '\n';
-
- if (!test_bit(Magic, &e->flags)) {
- sprintf(dp, "extension .%s\n", e->magic);
+ seq_puts(m, "flags: ");
+ for (int i = 0; i < ARRAY_SIZE(misc_flags); i++)
+ if (e->flags & misc_flags[i].flag)
+ seq_putc(m, misc_flags[i].c);
+ seq_putc(m, '\n');
+
+ if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) {
+ /* The program does the matching. */
+ } else if (!test_bit(MISC_FMT_MAGIC_BIT, &e->flags)) {
+ seq_printf(m, "extension .%s\n", e->magic);
} else {
- dp += sprintf(dp, "offset %i\nmagic ", e->offset);
- dp = bin2hex(dp, e->magic, e->size);
+ seq_printf(m, "offset %i\nmagic ", e->offset);
+ bm_seq_hex(m, e->magic, e->size);
if (e->mask) {
- dp += sprintf(dp, "\nmask ");
- dp = bin2hex(dp, e->mask, e->size);
+ seq_puts(m, "\nmask ");
+ bm_seq_hex(m, e->mask, e->size);
}
- *dp++ = '\n';
- *dp = '\0';
+ seq_putc(m, '\n');
}
+ return 0;
}
-static struct inode *bm_get_inode(struct super_block *sb, int mode)
+static struct inode *bm_get_inode(struct super_block *sb, umode_t mode)
{
struct inode *inode = new_inode(sb);
@@ -663,14 +1141,14 @@ static struct binfmt_misc *i_binfmt_misc(struct inode *inode)
* entry is removed or the filesystem is unmounted and the super block is
* shutdown.
*
- * If the ->evict call was not caused by a super block shutdown but by a write
- * to remove the entry or all entries via bm_{entry,status}_write() the entry
- * will have already been removed from the list. We keep the list_empty() check
- * to make that explicit.
+ * If the ->evict call was not caused by a super block shutdown but by
+ * removing the entry via bm_{entry,status}_write() or unlink(2) the entry
+ * will have already been removed from the list. We keep the hlist_unhashed()
+ * check to make that explicit.
*/
static void bm_evict_inode(struct inode *inode)
{
- Node *e = inode->i_private;
+ struct binfmt_misc_entry *e = inode->i_private;
clear_inode(inode);
@@ -678,89 +1156,259 @@ static void bm_evict_inode(struct inode *inode)
struct binfmt_misc *misc;
misc = i_binfmt_misc(inode);
- write_lock(&misc->entries_lock);
- if (!list_empty(&e->list))
- list_del_init(&e->list);
- write_unlock(&misc->entries_lock);
+ spin_lock(&misc->entries_lock);
+ if (!hlist_unhashed(&e->node))
+ hlist_del_init_rcu(&e->node);
+ spin_unlock(&misc->entries_lock);
put_binfmt_handler(e);
}
}
/**
+ * unlink_binfmt_handler - unhash a binary type handler
+ * @misc: handle to binfmt_misc instance
+ * @e: binary type handler to unhash
+ *
+ * Adding and removing entries via bm_{entry,register,status}_write() and
+ * unlink(2) happens under the exclusively held inode lock of the root
+ * dentry keeping the list stable for writers. load_misc_binary() walks it
+ * concurrently under SRCU. The entries_lock is only held around the actual
+ * unlink to serialize against bm_evict_inode() which unlinks entries
+ * during umount without holding the root inode lock.
+ */
+static void unlink_binfmt_handler(struct binfmt_misc *misc,
+ struct binfmt_misc_entry *e)
+{
+ spin_lock(&misc->entries_lock);
+ hlist_del_init_rcu(&e->node);
+ spin_unlock(&misc->entries_lock);
+}
+
+/**
* remove_binfmt_handler - remove a binary type handler
* @misc: handle to binfmt_misc instance
* @e: binary type handler to remove
*
* Remove a binary type handler from the list of binary type handlers and
- * remove its associated dentry. This is called from
- * binfmt_{entry,status}_write(). In the future, we might want to think about
- * adding a proper ->unlink() method to binfmt_misc instead of forcing caller's
- * to use writes to files in order to delete binary type handlers. But it has
- * worked for so long that it's not a pressing issue.
+ * remove its associated dentry.
*/
-static void remove_binfmt_handler(struct binfmt_misc *misc, Node *e)
+static void remove_binfmt_handler(struct binfmt_misc *misc,
+ struct binfmt_misc_entry *e)
{
- write_lock(&misc->entries_lock);
- list_del_init(&e->list);
- write_unlock(&misc->entries_lock);
+ unlink_binfmt_handler(misc, e);
locked_recursive_removal(e->dentry, NULL);
}
+/* Remove @e unless it was already removed. */
+static void bm_remove_entry(struct binfmt_misc_entry *e, struct super_block *sb)
+{
+ struct inode *root = bm_root_inode(sb);
+
+ inode_lock_nested(root, I_MUTEX_PARENT);
+ /* A staged entry is not hashed; the dentry says if it was removed. */
+ if (!d_unhashed(e->dentry))
+ remove_binfmt_handler(i_binfmt_misc(root), e);
+ inode_unlock(root);
+}
+
+/* Remove all entries of the binfmt_misc instance @misc belonging to @sb. */
+static void bm_remove_all_entries(struct binfmt_misc *misc,
+ struct super_block *sb)
+{
+ struct inode *root = bm_root_inode(sb);
+ struct dentry *child = NULL;
+
+ inode_lock_nested(root, I_MUTEX_PARENT);
+ /*
+ * Walk the directory rather than the search list: a staged entry
+ * is in the former but not yet in the latter. The control files
+ * carry no entry and stay.
+ */
+ while ((child = find_next_child(sb->s_root, child))) {
+ struct binfmt_misc_entry *e = d_inode(child)->i_private;
+
+ if (e)
+ remove_binfmt_handler(misc, e);
+ }
+ inode_unlock(root);
+}
+
+/**
+ * bm_unlink - remove a binary type handler via unlink(2)
+ * @dir: inode of the root directory
+ * @dentry: entry file to remove
+ *
+ * Removing the entry file removes its binary type handler, exactly like
+ * writing -1 to it does. The status and register control files can't be
+ * removed. The VFS calls this with the root inode lock held which
+ * serializes against the write based add and remove paths.
+ */
+static int bm_unlink(struct inode *dir, struct dentry *dentry)
+{
+ struct binfmt_misc_entry *e = d_inode(dentry)->i_private;
+
+ if (!e)
+ return -EPERM;
+
+ unlink_binfmt_handler(i_binfmt_misc(dir), e);
+ return simple_unlink(dir, dentry);
+}
+
+static const struct inode_operations bm_dir_inode_operations = {
+ .lookup = simple_lookup,
+ .unlink = bm_unlink,
+};
+
/* /<entry> */
-static ssize_t
-bm_entry_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
+static int bm_entry_open(struct inode *inode, struct file *file)
{
- Node *e = file_inode(file)->i_private;
- ssize_t res;
- char *page;
+ int ret;
- page = kmalloc(PAGE_SIZE, GFP_KERNEL);
- if (!page)
- return -ENOMEM;
+ ret = single_open(file, bm_entry_show, inode->i_private);
+ if (ret)
+ return ret;
+
+ /* seq_open() clears FMODE_PWRITE, bm_entry_write() takes any offset */
+ if (file->f_mode & FMODE_WRITE)
+ file->f_mode |= FMODE_PWRITE;
+ return 0;
+}
+
+/*
+ * Longest '+<name> <path>' a write can spell, and with it the longest
+ * command an entry file takes: the two delimiters and a newline on top of
+ * the two names.
+ */
+#define MAX_BINDING_LENGTH (BINFMT_MISC_INTERP_NAME_MAX + PATH_MAX + 3)
+
+/**
+ * bm_entry_add_interp - bind another interpreter to a staged entry
+ * @e: the entry
+ * @file: the entry file being written to, for its credentials
+ * @buf: the '+<name> <path>' command, parsed in place and owned by the caller
+ * @count: its length
+ *
+ * A 'D' entry is registered outside the search list, which is what leaves
+ * it open to being configured: it cannot be matched, so no exec can be
+ * holding its interpreters and the set can still grow. Its first enable
+ * publishes it and ends that. One interpreter per write, up to
+ * BINFMT_MISC_INTERP_MAX of them, none of which has to fit in a register
+ * string.
+ *
+ * Return: @count on success, a negative errno on failure
+ */
+static ssize_t bm_entry_add_interp(struct binfmt_misc_entry *e,
+ struct file *file, char *buf, size_t count)
+{
+ struct file *f __free(close_interp_file) = NULL;
+ struct inode *root = bm_root_inode(file_inode(file)->i_sb);
+ size_t nlen, plen;
+ char *name, *path;
+ int retval;
+
+ /* Settled before the open: type is fixed, publication is permanent. */
+ if (!test_bit(MISC_FMT_BPF_BIT, &e->flags))
+ return -EINVAL;
+ if (!hlist_unhashed_lockless(&e->node))
+ return -EBUSY;
+
+ /* '+<name> <path>': the path is everything past the first space. */
+ name = buf + 1;
+ path = strchr(name, ' ');
+ if (!path)
+ return -EINVAL;
+ *path++ = '\0';
- entry_status(e, page);
+ plen = strlen(path);
+ /* The command has to end at the write, like a register string. */
+ if (path + plen != buf + count)
+ return -EINVAL;
+ if (plen && path[plen - 1] == '\n')
+ path[--plen] = '\0';
+ /* Resolved now, so a relative path would name the writer's cwd. */
+ if (path[0] != '/')
+ return -EINVAL;
- res = simple_read_from_buffer(buf, nbytes, ppos, page, strlen(page));
+ nlen = path - name - 1;
+ if (!nlen || nlen > BINFMT_MISC_INTERP_NAME_MAX)
+ return -EINVAL;
+ /* The name prints between delimiters, so keep it a printable word. */
+ for (const char *p = name; *p; p++)
+ if (!isascii(*p) || !isgraph(*p))
+ return -EINVAL;
+
+ /* Opened before the lock: resolving it may walk this very filesystem. */
+ f = open_interp_file(file->f_cred, path);
+ if (IS_ERR(f))
+ return PTR_ERR(f);
+
+ inode_lock(root);
+ if (d_unhashed(e->dentry))
+ retval = -ENOENT; /* removed while we were opening it */
+ else if (!hlist_unhashed(&e->node))
+ retval = -EBUSY; /* published while we were opening it */
+ else
+ retval = entry_attach_interpreter(e, name, path, f);
+ inode_unlock(root);
+ if (retval)
+ return retval;
- kfree(page);
- return res;
+ /* The file is owned by the entry now. */
+ retain_and_null_ptr(f);
+ return count;
}
static ssize_t bm_entry_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
struct inode *inode = file_inode(file);
- Node *e = inode->i_private;
- int res = parse_command(buffer, count);
+ struct binfmt_misc_entry *e = inode->i_private;
+ char *buf __free(kfree) = NULL;
+ int res;
+
+ /* A binding is the longest command this file takes. */
+ if (count > MAX_BINDING_LENGTH)
+ return -E2BIG;
+
+ buf = memdup_user_nul(buffer, count);
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ /* '+<name> <path>' binds an interpreter, everything else toggles. */
+ if (buf[0] == '+')
+ return bm_entry_add_interp(e, file, buf, count);
+
+ res = parse_command(buf, count);
switch (res) {
- case 1:
- /* Disable this handler. */
- clear_bit(Enabled, &e->flags);
- break;
- case 2:
- /* Enable this handler. */
- set_bit(Enabled, &e->flags);
+ case BM_CMD_DISABLE:
+ clear_bit(MISC_FMT_ENABLED_BIT, &e->flags);
break;
- case 3:
- /* Delete this handler. */
- inode = d_inode(inode->i_sb->s_root);
- inode_lock_nested(inode, I_MUTEX_PARENT);
+ case BM_CMD_ENABLE: {
+ struct inode *root = bm_root_inode(inode->i_sb);
/*
- * In order to add new element or remove elements from the list
- * via bm_{entry,register,status}_write() inode_lock() on the
- * root inode must be held.
- * The lock is exclusive ensuring that the list can't be
- * modified. Only load_misc_binary() can access but does so
- * read-only. So we only need to take the write lock when we
- * actually remove the entry from the list.
+ * The first enable publishes a 'D' entry into the search
+ * list, whole. The lock keeps that ordered against a second
+ * enable, against removal - a removed entry has nothing left
+ * to publish - and against binding: what can be matched can
+ * no longer be configured.
*/
- if (!list_empty(&e->list))
- remove_binfmt_handler(i_binfmt_misc(inode), e);
-
- inode_unlock(inode);
+ inode_lock(root);
+ set_bit(MISC_FMT_ENABLED_BIT, &e->flags);
+ if (hlist_unhashed(&e->node) && !d_unhashed(e->dentry)) {
+ struct binfmt_misc *misc = i_binfmt_misc(inode);
+
+ spin_lock(&misc->entries_lock);
+ hlist_add_head_rcu(&e->node, &misc->entries);
+ spin_unlock(&misc->entries_lock);
+ }
+ inode_unlock(root);
+ break;
+ }
+ case BM_CMD_REMOVE:
+ bm_remove_entry(e, inode->i_sb);
break;
default:
return res;
@@ -770,15 +1418,17 @@ static ssize_t bm_entry_write(struct file *file, const char __user *buffer,
}
static const struct file_operations bm_entry_operations = {
- .read = bm_entry_read,
+ .open = bm_entry_open,
+ .read = seq_read,
.write = bm_entry_write,
- .llseek = default_llseek,
+ .llseek = seq_lseek,
+ .release = single_release,
};
/* /register */
/* add to filesystem */
-static int add_entry(Node *e, struct super_block *sb)
+static int add_entry(struct binfmt_misc_entry *e, struct super_block *sb)
{
struct dentry *dentry = simple_start_creating(sb->s_root, e->name);
struct inode *inode;
@@ -793,16 +1443,18 @@ static int add_entry(Node *e, struct super_block *sb)
return -ENOMEM;
}
- refcount_set(&e->users, 1);
e->dentry = dentry;
inode->i_private = e;
inode->i_fop = &bm_entry_operations;
d_make_persistent(dentry, inode);
- misc = i_binfmt_misc(inode);
- write_lock(&misc->entries_lock);
- list_add(&e->list, &misc->entries);
- write_unlock(&misc->entries_lock);
+ /* A 'D' entry stays out of the search list until its first enable. */
+ if (test_bit(MISC_FMT_ENABLED_BIT, &e->flags)) {
+ misc = i_binfmt_misc(inode);
+ spin_lock(&misc->entries_lock);
+ hlist_add_head_rcu(&e->node, &misc->entries);
+ spin_unlock(&misc->entries_lock);
+ }
simple_done_creating(dentry);
return 0;
}
@@ -810,44 +1462,41 @@ static int add_entry(Node *e, struct super_block *sb)
static ssize_t bm_register_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
- Node *e;
+ struct binfmt_misc_entry *e __free(put_binfmt_handler) = NULL;
struct super_block *sb = file_inode(file)->i_sb;
- int err = 0;
- struct file *f = NULL;
+ int err;
e = create_entry(buffer, count);
-
if (IS_ERR(e))
return PTR_ERR(e);
+ if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) {
+ e->bpf_ops = binfmt_misc_get_ops(sb->s_user_ns, e->bpf_ops_name);
+ if (!e->bpf_ops) {
+ pr_notice("register: no bpf handler named %s\n",
+ e->bpf_ops_name);
+ return -ENOENT;
+ }
+ }
+
if (e->flags & MISC_FMT_OPEN_FILE) {
- /*
- * Now that we support unprivileged binfmt_misc mounts make
- * sure we use the credentials that the register @file was
- * opened with to also open the interpreter. Before that this
- * didn't matter much as only a privileged process could open
- * the register file.
- */
- scoped_with_creds(file->f_cred)
- f = open_exec(e->interpreter);
- if (IS_ERR(f)) {
- pr_notice("register: failed to install interpreter file %s\n",
- e->interpreter);
- kfree(e);
+ struct file *f = open_interp_file(file->f_cred, e->interpreter);
+
+ if (IS_ERR(f))
return PTR_ERR(f);
+ err = entry_attach_interpreter(e, "", e->interpreter, f);
+ if (err) {
+ close_interp_file(f);
+ return err;
}
- e->interp_file = f;
}
err = add_entry(e, sb);
- if (err) {
- if (f) {
- exe_file_allow_write_access(f);
- filp_close(f, NULL);
- }
- kfree(e);
+ if (err)
return err;
- }
+
+ /* The entry is owned by its inode now. */
+ retain_and_null_ptr(e);
return count;
}
@@ -862,10 +1511,10 @@ static ssize_t
bm_status_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{
struct binfmt_misc *misc;
- char *s;
+ const char *s;
misc = i_binfmt_misc(file_inode(file));
- s = misc->enabled ? "enabled\n" : "disabled\n";
+ s = READ_ONCE(misc->enabled) ? "enabled\n" : "disabled\n";
return simple_read_from_buffer(buf, nbytes, ppos, s, strlen(s));
}
@@ -873,38 +1522,18 @@ static ssize_t bm_status_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
struct binfmt_misc *misc;
- int res = parse_command(buffer, count);
- Node *e, *next;
- struct inode *inode;
+ int res = read_command(buffer, count);
misc = i_binfmt_misc(file_inode(file));
switch (res) {
- case 1:
- /* Disable all handlers. */
- misc->enabled = false;
+ case BM_CMD_DISABLE:
+ WRITE_ONCE(misc->enabled, false);
break;
- case 2:
- /* Enable all handlers. */
- misc->enabled = true;
+ case BM_CMD_ENABLE:
+ WRITE_ONCE(misc->enabled, true);
break;
- case 3:
- /* Delete all handlers. */
- inode = d_inode(file_inode(file)->i_sb->s_root);
- inode_lock_nested(inode, I_MUTEX_PARENT);
-
- /*
- * In order to add new element or remove elements from the list
- * via bm_{entry,register,status}_write() inode_lock() on the
- * root inode must be held.
- * The lock is exclusive ensuring that the list can't be
- * modified. Only load_misc_binary() can access but does so
- * read-only. So we only need to take the write lock when we
- * actually remove the entry from the list.
- */
- list_for_each_entry_safe(e, next, &misc->entries, list)
- remove_binfmt_handler(misc, e);
-
- inode_unlock(inode);
+ case BM_CMD_REMOVE:
+ bm_remove_all_entries(misc, file_inode(file)->i_sb);
break;
default:
return res;
@@ -921,7 +1550,7 @@ static const struct file_operations bm_status_operations = {
/* Superblock handling */
-static const struct super_operations s_ops = {
+static const struct super_operations bm_super_ops = {
.statfs = simple_statfs,
.evict_inode = bm_evict_inode,
};
@@ -972,10 +1601,10 @@ static int bm_fill_super(struct super_block *sb, struct fs_context *fc)
if (!misc)
return -ENOMEM;
- INIT_LIST_HEAD(&misc->entries);
- rwlock_init(&misc->entries_lock);
+ INIT_HLIST_HEAD(&misc->entries);
+ spin_lock_init(&misc->entries_lock);
- /* Pairs with smp_load_acquire() in load_binfmt_misc(). */
+ /* Pairs with smp_load_acquire() in current_binfmt_misc(). */
smp_store_release(&user_ns->binfmt_misc, misc);
}
@@ -989,12 +1618,15 @@ static int bm_fill_super(struct super_block *sb, struct fs_context *fc)
* is true. Instead, if someone mounts binfmt_misc for the first time or
* again we simply reset ->enabled to true.
*/
- misc->enabled = true;
+ WRITE_ONCE(misc->enabled, true);
err = simple_fill_super(sb, BINFMTFS_MAGIC, bm_files);
- if (!err)
- sb->s_op = &s_ops;
- return err;
+ if (err)
+ return err;
+
+ sb->s_op = &bm_super_ops;
+ d_inode(sb->s_root)->i_op = &bm_dir_inode_operations;
+ return 0;
}
static void bm_free(struct fs_context *fc)
@@ -1053,6 +1685,8 @@ static void __exit exit_misc_binfmt(void)
{
unregister_binfmt(&misc_format);
unregister_filesystem(&bm_fs_type);
+ /* Flush pending bm_entry_free_rcu() callbacks before the text goes. */
+ srcu_barrier(&bm_entries_srcu);
}
core_initcall(init_misc_binfmt);
diff --git a/fs/binfmt_misc_bpf.c b/fs/binfmt_misc_bpf.c
new file mode 100644
index 000000000000..91576ff05911
--- /dev/null
+++ b/fs/binfmt_misc_bpf.c
@@ -0,0 +1,434 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * BPF-backed binary type handlers for binfmt_misc.
+ *
+ * A handler is a struct binfmt_misc_ops struct_ops map. Loading and
+ * registering it makes the handler available under its name in the user
+ * namespace it was registered in. A binfmt_misc 'B' entry activates it:
+ *
+ * echo ':entry:B::::<handler-name>:' > <binfmt_misc>/register
+ *
+ * The entry can bind the interpreters the handler may run its binaries
+ * with, each opened by the write that binds it and selected by name per
+ * exec. An entry registered with 'D' is not matchable yet, which is what
+ * leaves it open to being given them:
+ *
+ * echo ':entry:B::::<handler-name>:D' > <binfmt_misc>/register
+ * echo '+<name> <path>' > <binfmt_misc>/entry
+ * echo 1 > <binfmt_misc>/entry
+ */
+
+#include <linux/binfmt_misc.h>
+#include <linux/binfmts.h>
+#include <linux/bpf.h>
+#include <linux/bpf_verifier.h>
+#include <linux/btf.h>
+#include <linux/btf_ids.h>
+#include <linux/cred.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/limits.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/user_namespace.h>
+
+struct bm_bpf_ops_reg {
+ struct list_head list;
+ const struct binfmt_misc_ops *ops;
+ struct bpf_link *link;
+ struct user_namespace *user_ns;
+};
+
+static DEFINE_SPINLOCK(bm_bpf_ops_lock);
+static LIST_HEAD(bm_bpf_ops_list);
+
+static struct bpf_struct_ops bpf_binfmt_misc_ops;
+
+static struct bm_bpf_ops_reg *bm_bpf_ops_find(const struct user_namespace *user_ns,
+ const char *name)
+{
+ struct bm_bpf_ops_reg *reg;
+
+ lockdep_assert_held(&bm_bpf_ops_lock);
+
+ list_for_each_entry(reg, &bm_bpf_ops_list, list) {
+ if (reg->user_ns == user_ns && !strcmp(reg->ops->name, name))
+ return reg;
+ }
+ return NULL;
+}
+
+/**
+ * binfmt_misc_get_ops - look up a bpf binary type handler by name
+ * @user_ns: user namespace of the binfmt_misc instance
+ * @name: name the handler was registered under
+ *
+ * Look for a handler named @name registered in @user_ns. A handler is not
+ * inherited from ancestor user namespaces: an entry can only name a handler
+ * registered in the same user namespace as its instance. The returned handler
+ * stays callable until binfmt_misc_put_ops() even if the backing struct_ops
+ * map is detached or deleted in the meantime.
+ *
+ * Return: the handler on success, NULL on failure
+ */
+const struct binfmt_misc_ops *binfmt_misc_get_ops(struct user_namespace *user_ns,
+ const char *name)
+{
+ struct bm_bpf_ops_reg *reg;
+
+ guard(spinlock)(&bm_bpf_ops_lock);
+
+ reg = bm_bpf_ops_find(user_ns, name);
+ if (!reg)
+ return NULL;
+ if (!bpf_struct_ops_get(reg->ops))
+ return NULL;
+ return reg->ops;
+}
+
+void binfmt_misc_put_ops(const struct binfmt_misc_ops *ops)
+{
+ bpf_struct_ops_put(ops);
+}
+
+bool bpf_prog_is_binfmt_misc_ops(const struct bpf_prog *prog)
+{
+ return prog->type == BPF_PROG_TYPE_STRUCT_OPS &&
+ prog->aux->st_ops == &bpf_binfmt_misc_ops;
+}
+
+/*
+ * Replace the staged interpreter selection: naming a path drops a bound
+ * file, selecting a bound interpreter carries its file along.
+ */
+static void bm_bpf_stage_selection(struct linux_binprm *bprm, char *path,
+ struct file *f)
+{
+ if (bprm->bpf_interp_file)
+ fput(bprm->bpf_interp_file);
+ kfree(bprm->bpf_interp);
+ bprm->bpf_interp = path;
+ bprm->bpf_interp_file = f;
+}
+
+__bpf_kfunc_start_defs();
+
+/**
+ * bpf_binprm_set_interp - select the interpreter for the current exec
+ * @bprm: binary that is being executed
+ * @path: absolute path to the interpreter
+ * @path__sz: size of the @path buffer, including the terminating NUL
+ *
+ * To be called from the load program of a struct binfmt_misc_ops handler
+ * before returning zero; the verifier rejects the call from any other
+ * program, including the handler's own match program. The path is opened
+ * with the credentials of the task doing the exec after the program
+ * returns. Calling it again replaces the selection, as does selecting an
+ * interpreter the entry bound with bpf_binprm_select_interp().
+ *
+ * Return: 0 on success, a negative errno on failure
+ */
+__bpf_kfunc int bpf_binprm_set_interp(struct linux_binprm *bprm,
+ const char *path, size_t path__sz)
+{
+ size_t len;
+ char *interp;
+
+ if (!path__sz)
+ return -EINVAL;
+ len = strnlen(path, path__sz);
+ if (len == path__sz)
+ return -EINVAL;
+ if (path[0] != '/')
+ return -EINVAL;
+ if (len >= PATH_MAX)
+ return -ENAMETOOLONG;
+
+ interp = kmemdup_nul(path, len, GFP_KERNEL);
+ if (!interp)
+ return -ENOMEM;
+
+ bm_bpf_stage_selection(bprm, interp, NULL);
+ return 0;
+}
+
+/**
+ * bpf_binprm_select_interp - run this exec under an interpreter the entry bound
+ * @bprm: binary that is being executed
+ * @name: name the interpreter was registered under
+ * @name__sz: size of the @name buffer, including the terminating NUL
+ *
+ * To be called from the load program of a struct binfmt_misc_ops handler
+ * instead of bpf_binprm_set_interp(). It selects one of the interpreters
+ * the matched entry was registered with, each of which was opened once when
+ * the entry was registered. Nothing is resolved at exec time, so no
+ * filesystem view can redirect the interpreter.
+ *
+ * The interpreter runs under the path the entry registered it under.
+ * Calling it again replaces the selection.
+ *
+ * Return: 0 on success, -ENOENT if the matched entry bound no interpreter
+ * of that name, a negative errno on failure
+ */
+__bpf_kfunc int bpf_binprm_select_interp(struct linux_binprm *bprm,
+ const char *name, size_t name__sz)
+{
+ const struct binfmt_misc_interp *interp;
+ size_t len;
+ char *path;
+
+ if (!name__sz)
+ return -EINVAL;
+ len = strnlen(name, name__sz);
+ if (len == name__sz || !len)
+ return -EINVAL;
+
+ interp = binfmt_misc_find_interp(bprm->bpf_interps, name);
+ if (!interp)
+ return -ENOENT;
+
+ path = kstrdup(interp->path, GFP_KERNEL);
+ if (!path)
+ return -ENOMEM;
+
+ bm_bpf_stage_selection(bprm, path, get_file(interp->file));
+ return 0;
+}
+
+/**
+ * bpf_binprm_set_interp_arg - set a single argument for the interpreter
+ * @bprm: binary that is being executed
+ * @arg: argument to pass to the interpreter
+ * @arg__sz: size of the @arg buffer, including the terminating NUL
+ *
+ * To be called from the load program of a struct binfmt_misc_ops handler. The
+ * argument is passed to the interpreter ahead of the binary, mirroring the
+ * single optional argument of a #! interpreter line. Calling it again
+ * replaces the argument.
+ *
+ * Return: 0 on success, a negative errno on failure
+ */
+__bpf_kfunc int bpf_binprm_set_interp_arg(struct linux_binprm *bprm,
+ const char *arg, size_t arg__sz)
+{
+ size_t len;
+ char *val;
+
+ if (!arg__sz)
+ return -EINVAL;
+ len = strnlen(arg, arg__sz);
+ if (len == arg__sz)
+ return -EINVAL;
+ if (!len)
+ return -EINVAL;
+
+ val = kmemdup_nul(arg, len, GFP_KERNEL);
+ if (!val)
+ return -ENOMEM;
+
+ kfree(bprm->bpf_interp_arg);
+ bprm->bpf_interp_arg = val;
+ return 0;
+}
+
+/**
+ * bpf_binprm_set_flags - choose the interpreter invocation flags for this exec
+ * @bprm: binary that is being executed
+ * @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, 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.
+ * 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
+ */
+__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_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. */
+ if ((flags & BPF_BINPRM_TRANSPARENT) && (flags & BPF_BINPRM_PRESERVE_ARGV0))
+ return -EINVAL;
+
+ bprm->bpf_flags = flags;
+ return 0;
+}
+
+__bpf_kfunc_end_defs();
+
+BTF_KFUNCS_START(bm_bpf_kfunc_ids)
+BTF_ID_FLAGS(func, bpf_binprm_set_interp, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_binprm_select_interp, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_binprm_set_interp_arg, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_binprm_set_flags, KF_SLEEPABLE)
+BTF_KFUNCS_END(bm_bpf_kfunc_ids)
+
+static int bm_bpf_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id)
+{
+ if (!btf_id_set8_contains(&bm_bpf_kfunc_ids, kfunc_id))
+ return 0;
+ if (prog->type != BPF_PROG_TYPE_STRUCT_OPS)
+ return -EACCES;
+ /* ->st_ops is unset during the cfg pass; enforced once it is set. */
+ if (!prog->aux->st_ops)
+ return 0;
+ /* Only the load program decides how a binary is run. */
+ if (bpf_prog_is_binfmt_misc_ops(prog) &&
+ prog->aux->attach_st_ops_member_off == offsetof(struct binfmt_misc_ops, load))
+ return 0;
+ return -EACCES;
+}
+
+static const struct btf_kfunc_id_set bm_bpf_kfunc_set = {
+ .owner = THIS_MODULE,
+ .set = &bm_bpf_kfunc_ids,
+ .filter = bm_bpf_kfunc_filter,
+};
+
+static bool bm_bpf_ops__match(struct linux_binprm *bprm)
+{
+ return false;
+}
+
+static int bm_bpf_ops__load(struct linux_binprm *bprm)
+{
+ return 0;
+}
+
+static struct binfmt_misc_ops bm_bpf_ops_stubs = {
+ .match = bm_bpf_ops__match,
+ .load = bm_bpf_ops__load,
+};
+
+static int bm_bpf_init(struct btf *btf)
+{
+ return register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS,
+ &bm_bpf_kfunc_set);
+}
+
+static int bm_bpf_check_member(const struct btf_type *t,
+ const struct btf_member *member,
+ const struct bpf_prog *prog)
+{
+ u32 moff = __btf_member_bit_offset(t, member) / 8;
+
+ switch (moff) {
+ case offsetof(struct binfmt_misc_ops, match):
+ case offsetof(struct binfmt_misc_ops, load):
+ /* Reliable file reads at exec time require sleeping. */
+ if (!prog->sleepable)
+ return -EINVAL;
+ break;
+ }
+ return 0;
+}
+
+static int bm_bpf_init_member(const struct btf_type *t,
+ const struct btf_member *member,
+ void *kdata, const void *udata)
+{
+ const struct binfmt_misc_ops *uops = udata;
+ struct binfmt_misc_ops *ops = kdata;
+ u32 moff = __btf_member_bit_offset(t, member) / 8;
+
+ switch (moff) {
+ case offsetof(struct binfmt_misc_ops, name):
+ if (bpf_obj_name_cpy(ops->name, uops->name,
+ sizeof(ops->name)) <= 0)
+ return -EINVAL;
+ return 1;
+ }
+ return 0;
+}
+
+static int bm_bpf_validate(void *kdata)
+{
+ struct binfmt_misc_ops *ops = kdata;
+
+ if (!ops->match || !ops->load)
+ return -EINVAL;
+ return 0;
+}
+
+static int bm_bpf_reg(void *kdata, struct bpf_link *link)
+{
+ struct binfmt_misc_ops *ops = kdata;
+ struct bm_bpf_ops_reg *reg;
+
+ reg = kzalloc_obj(*reg, GFP_KERNEL_ACCOUNT);
+ if (!reg)
+ return -ENOMEM;
+
+ reg->ops = ops;
+ reg->link = link;
+ reg->user_ns = get_user_ns(current_user_ns());
+
+ guard(spinlock)(&bm_bpf_ops_lock);
+
+ if (bm_bpf_ops_find(reg->user_ns, ops->name)) {
+ put_user_ns(reg->user_ns);
+ kfree(reg);
+ return -EEXIST;
+ }
+
+ list_add(&reg->list, &bm_bpf_ops_list);
+ return 0;
+}
+
+static void bm_bpf_unreg(void *kdata, struct bpf_link *link)
+{
+ struct bm_bpf_ops_reg *reg;
+
+ guard(spinlock)(&bm_bpf_ops_lock);
+
+ list_for_each_entry(reg, &bm_bpf_ops_list, list) {
+ if (reg->ops == kdata && reg->link == link) {
+ list_del(&reg->list);
+ put_user_ns(reg->user_ns);
+ kfree(reg);
+ return;
+ }
+ }
+}
+
+static const struct bpf_verifier_ops bm_bpf_verifier_ops = {
+ .get_func_proto = bpf_base_func_proto,
+ .is_valid_access = bpf_tracing_btf_ctx_access,
+};
+
+static struct bpf_struct_ops bpf_binfmt_misc_ops = {
+ .verifier_ops = &bm_bpf_verifier_ops,
+ .init = bm_bpf_init,
+ .check_member = bm_bpf_check_member,
+ .init_member = bm_bpf_init_member,
+ .validate = bm_bpf_validate,
+ .reg = bm_bpf_reg,
+ .unreg = bm_bpf_unreg,
+ .cfi_stubs = &bm_bpf_ops_stubs,
+ .name = "binfmt_misc_ops",
+ .owner = THIS_MODULE,
+};
+
+static int __init bm_bpf_struct_ops_init(void)
+{
+ return register_bpf_struct_ops(&bpf_binfmt_misc_ops, binfmt_misc_ops);
+}
+late_initcall(bm_bpf_struct_ops_init);
diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
index f1863a891db6..5b7d03e4fc6d 100644
--- a/fs/bpf_fs_kfuncs.c
+++ b/fs/bpf_fs_kfuncs.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2024 Google LLC. */
+#include <linux/binfmt_misc.h>
#include <linux/bpf.h>
#include <linux/bpf_lsm.h>
#include <linux/btf.h>
@@ -392,10 +393,25 @@ BTF_ID_FLAGS(func, bpf_remove_dentry_xattr, KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_real_data_inode, KF_SLEEPABLE | KF_RET_NULL)
BTF_KFUNCS_END(bpf_fs_kfunc_set_ids)
+/* Side-effecting kfuncs that stay exclusive to LSM programs. */
+BTF_SET_START(bpf_fs_kfunc_lsm_only_ids)
+BTF_ID(func, bpf_set_dentry_xattr)
+BTF_ID(func, bpf_remove_dentry_xattr)
+BTF_SET_END(bpf_fs_kfunc_lsm_only_ids)
+
static int bpf_fs_kfuncs_filter(const struct bpf_prog *prog, u32 kfunc_id)
{
- if (!btf_id_set8_contains(&bpf_fs_kfunc_set_ids, kfunc_id) ||
- prog->type == BPF_PROG_TYPE_LSM)
+ if (!btf_id_set8_contains(&bpf_fs_kfunc_set_ids, kfunc_id))
+ return 0;
+ if (prog->type == BPF_PROG_TYPE_LSM)
+ return 0;
+ if (prog->type != BPF_PROG_TYPE_STRUCT_OPS)
+ return -EACCES;
+ /* ->st_ops is unset during the cfg pass; enforced once it is set. */
+ if (!prog->aux->st_ops)
+ return 0;
+ if (bpf_prog_is_binfmt_misc_ops(prog) &&
+ !btf_id_set_contains(&bpf_fs_kfunc_lsm_only_ids, kfunc_id))
return 0;
return -EACCES;
}
@@ -438,7 +454,13 @@ static const struct btf_kfunc_id_set bpf_fs_kfunc_set = {
static int __init bpf_fs_kfuncs_init(void)
{
- return register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, &bpf_fs_kfunc_set);
+ int ret;
+
+ ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, &bpf_fs_kfunc_set);
+ if (ret || !IS_ENABLED(CONFIG_BINFMT_MISC_BPF))
+ return ret;
+ return register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS,
+ &bpf_fs_kfunc_set);
}
late_initcall(bpf_fs_kfuncs_init);
diff --git a/fs/exec.c b/fs/exec.c
index c7b8f2d6366c..a14f28b15607 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1102,6 +1102,17 @@ void __set_task_comm(struct task_struct *tsk, const char *buf, bool exec)
}
/*
+ * The file the process presents as: its exe link and comm. A transparent
+ * dispatch presents as the binary, which is bprm->executable.
+ */
+static struct file *bprm_identity_file(const struct linux_binprm *bprm)
+{
+ if (bprm->interp_flags & BINPRM_FLAGS_TRANSPARENT_INTERP)
+ return bprm->executable;
+ return bprm->file;
+}
+
+/*
* Calling this is the point of no return. None of the failures will be
* seen by userspace since either the process is already taking a fatal
* signal (via de_thread() or coredump), or will have SEGV raised
@@ -1112,6 +1123,10 @@ int begin_new_exec(struct linux_binprm * bprm)
struct task_struct *me = current;
int retval;
+ /* A pending PT_INTERP substitution this format cannot consume. */
+ if (bprm->loader)
+ return -ENOEXEC;
+
/* Once we are committed compute the creds */
retval = bprm_creds_from_file(bprm);
if (retval)
@@ -1151,7 +1166,7 @@ int begin_new_exec(struct linux_binprm * bprm)
* not visible until then. Doing it here also ensures
* we don't race against replace_mm_exe_file().
*/
- retval = set_mm_exe_file(bprm->mm, bprm->file);
+ retval = set_mm_exe_file(bprm->mm, bprm_identity_file(bprm));
if (retval)
goto out;
@@ -1241,6 +1256,8 @@ int begin_new_exec(struct linux_binprm * bprm)
* Let's fix it up to be something reasonable.
*/
if (bprm->comm_from_dentry) {
+ struct file *comm_file = bprm_identity_file(bprm);
+
/*
* Hold RCU lock to keep the name from being freed behind our back.
* Use acquire semantics to make sure the terminating NUL from
@@ -1250,7 +1267,7 @@ int begin_new_exec(struct linux_binprm * bprm)
* detecting a concurrent rename and just want a terminated name.
*/
rcu_read_lock();
- __set_task_comm(me, smp_load_acquire(&bprm->file->f_path.dentry->d_name.name),
+ __set_task_comm(me, smp_load_acquire(&comm_file->f_path.dentry->d_name.name),
true);
rcu_read_unlock();
} else {
@@ -1291,10 +1308,17 @@ int begin_new_exec(struct linux_binprm * bprm)
/* Pass the opened binary to the interpreter. */
if (bprm->have_execfd) {
- retval = FD_ADD(0, bprm->executable);
- if (retval < 0)
- goto out_unlock;
+ struct file *executable = bprm->executable;
+
+ /* mm->exe_file carries its own write denial now so drop it. */
+ exe_file_allow_write_access(executable);
bprm->executable = NULL;
+ retval = FD_ADD(0, executable);
+ if (retval < 0) {
+ /* The reference was not consumed. */
+ fput(executable);
+ goto out_unlock;
+ }
bprm->execfd = retval;
}
return 0;
@@ -1394,6 +1418,39 @@ static void do_close_execat(struct file *file)
fput(file);
}
+/**
+ * bprm_open_interpreter - open the interpreter the binary asks for
+ * @bprm: binary that is being executed
+ * @path: the interpreter path named in the binary's PT_INTERP
+ *
+ * A binfmt_misc loader entry substitutes for the interpreter the binary
+ * names. Hand out the stashed substitute if there is one and open @path
+ * if there is not. The caller owns the reference either way and releases
+ * it like any other open_exec() one.
+ *
+ * Return: the interpreter on success, an ERR_PTR on failure
+ */
+struct file *bprm_open_interpreter(struct linux_binprm *bprm, const char *path)
+{
+ if (bprm->loader)
+ return no_free_ptr(bprm->loader);
+ return open_exec(path);
+}
+
+/**
+ * bprm_drop_loader - discard a PT_INTERP substitute that does not apply
+ * @bprm: binary that is being executed
+ *
+ * A binary without PT_INTERP has nothing to substitute for, so drop the
+ * override and let the binary load natively rather than have
+ * begin_new_exec() refuse it. A no-op once bprm_open_interpreter() took
+ * the substitute.
+ */
+void bprm_drop_loader(struct linux_binprm *bprm)
+{
+ do_close_execat(no_free_ptr(bprm->loader));
+}
+
static void free_bprm(struct linux_binprm *bprm)
{
if (bprm->mm) {
@@ -1413,11 +1470,16 @@ static void free_bprm(struct linux_binprm *bprm)
if (bprm->old_mm)
exec_mm_put_old(bprm->old_mm);
do_close_execat(bprm->file);
- if (bprm->executable)
- fput(bprm->executable);
+ /* An unconsumed PT_INTERP substitute from a binfmt_misc loader entry. */
+ bprm_drop_loader(bprm);
+ do_close_execat(bprm->executable);
/* If a binfmt changed the interp, free it. */
if (bprm->interp != bprm->filename)
kfree(bprm->interp);
+ kfree(bprm->bpf_interp);
+ if (bprm->bpf_interp_file)
+ fput(bprm->bpf_interp_file);
+ kfree(bprm->bpf_interp_arg);
kfree(bprm->fdpath);
kfree(bprm);
}
@@ -1729,19 +1791,23 @@ static int exec_binprm(struct linux_binprm *bprm)
if (!bprm->interpreter)
break;
+ /* A stashed PT_INTERP substitute belonged to the replaced file. */
+ bprm_drop_loader(bprm);
+
exec = bprm->file;
bprm->file = bprm->interpreter;
bprm->interpreter = NULL;
- exe_file_allow_write_access(exec);
if (unlikely(bprm->have_execfd)) {
if (bprm->executable) {
- fput(exec);
+ do_close_execat(exec);
return -ENOEXEC;
}
+ /* Kept for AT_EXECFD; the write denial rides along until hand-over. */
bprm->executable = exec;
- } else
- fput(exec);
+ } else {
+ do_close_execat(exec);
+ }
}
audit_bprm(bprm);
diff --git a/include/linux/binfmt_misc.h b/include/linux/binfmt_misc.h
new file mode 100644
index 000000000000..8045b10dd3e5
--- /dev/null
+++ b/include/linux/binfmt_misc.h
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_BINFMT_MISC_H
+#define _LINUX_BINFMT_MISC_H
+
+#include <linux/types.h>
+
+struct bpf_prog;
+struct file;
+struct linux_binprm;
+struct ucounts;
+struct user_namespace;
+
+#define BINFMT_MISC_OPS_NAME_MAX 16
+
+/* Longest name a 'B' entry can bind an interpreter under. */
+#define BINFMT_MISC_INTERP_NAME_MAX 32
+
+/* Most interpreters one entry can bind. */
+#define BINFMT_MISC_INTERP_MAX 100
+
+/**
+ * struct binfmt_misc_interp - an interpreter an entry was registered with
+ * @list: link in the entry's list, in registration order
+ * @file: the file, opened at registration and never resolved again
+ * @ucounts: the UCOUNT_BINFMT_MISC_INTERPRETERS charge the binding took
+ * @path: the path it was registered under, used as the name the interpreter
+ * runs under; stored after @name in the same allocation
+ * @name: the name the load program selects it by; empty for the fixed
+ * interpreter of a static 'F' entry
+ *
+ * Owned by the entry and living exactly as long as it does. The list head
+ * is handed to the handler's load program for the duration of one exec,
+ * which picks one with bpf_binprm_select_interp().
+ */
+struct binfmt_misc_interp {
+ struct list_head list;
+ struct file *file;
+ struct ucounts *ucounts;
+ const char *path;
+ char name[];
+};
+
+const struct binfmt_misc_interp *
+binfmt_misc_find_interp(const struct list_head *interps, const char *name);
+
+/**
+ * enum bpf_binprm_flags - per-exec invocation flags a load program can request
+ * @BPF_BINPRM_PRESERVE_ARGV0: keep the caller's argv[0] (like the 'P' flag)
+ * @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
+ * @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.
+ */
+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),
+ BPF_BINPRM_LOADER = (1ULL << 4),
+};
+
+/**
+ * struct binfmt_misc_ops - bpf-backed binary type handler
+ * @match: decide whether the handler applies to @bprm; consulted from the
+ * entry lookup walk like static magic and extension matching, in
+ * registration order with first-match-wins semantics; sleepable,
+ * so it can read the binary to decide, but the verifier rejects
+ * the interpreter selection kfuncs in it
+ * @load: select an interpreter for the matched @bprm via
+ * bpf_binprm_set_interp(), or one the entry bound via
+ * bpf_binprm_select_interp(), and return zero; a match is
+ * committed, so a failure fails the exec instead of falling
+ * through to later entries; -ENOEXEC does not fail the exec but
+ * moves on to the remaining binary formats
+ * @name: name that 'B' entries reference the handler by
+ */
+struct binfmt_misc_ops {
+ bool (*match)(struct linux_binprm *bprm);
+ int (*load)(struct linux_binprm *bprm);
+ char name[BINFMT_MISC_OPS_NAME_MAX];
+};
+
+#ifdef CONFIG_BINFMT_MISC_BPF
+const struct binfmt_misc_ops *binfmt_misc_get_ops(struct user_namespace *user_ns,
+ const char *name);
+void binfmt_misc_put_ops(const struct binfmt_misc_ops *ops);
+bool bpf_prog_is_binfmt_misc_ops(const struct bpf_prog *prog);
+#else
+static inline const struct binfmt_misc_ops *
+binfmt_misc_get_ops(struct user_namespace *user_ns, const char *name)
+{
+ return NULL;
+}
+
+static inline void binfmt_misc_put_ops(const struct binfmt_misc_ops *ops)
+{
+}
+
+static inline bool bpf_prog_is_binfmt_misc_ops(const struct bpf_prog *prog)
+{
+ return false;
+}
+#endif /* CONFIG_BINFMT_MISC_BPF */
+
+#endif /* _LINUX_BINFMT_MISC_H */
diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h
index 2c77e383e737..f686a37f7a0a 100644
--- a/include/linux/binfmts.h
+++ b/include/linux/binfmts.h
@@ -12,6 +12,16 @@ struct coredump_params;
#define CORENAME_MAX_SIZE 128
+/* Interpreter selection staged by a bpf binfmt_misc handler. */
+struct binfmt_misc_bpf {
+ /* interpreters the matched entry bound, selectable by name */
+ const struct list_head *bpf_interps;
+ const char *bpf_interp; /* interpreter selected by a bpf handler */
+ struct file *bpf_interp_file; /* the bound interpreter it selected */
+ const char *bpf_interp_arg; /* interpreter argument from a bpf handler */
+ u64 bpf_flags; /* enum bpf_binprm_flags from a bpf handler */
+};
+
/*
* This structure is used to hold the arguments that are used when loading binaries.
*/
@@ -55,6 +65,7 @@ struct linux_binprm {
is_check:1;
struct file *executable; /* Executable to pass to the interpreter */
struct file *interpreter;
+ struct file *loader;
struct file *file;
struct cred *cred; /* new credentials */
int unsafe; /* how unsafe this exec is (mask of LSM_UNSAFE_*) */
@@ -65,6 +76,7 @@ struct linux_binprm {
of the time same as filename, but could be
different for binfmt_{misc,script} */
const char *fdpath; /* generated filename for execveat */
+ struct binfmt_misc_bpf; /* bpf handler interpreter selection */
unsigned interp_flags;
int execfd; /* File descriptor of the executable */
unsigned long exec;
@@ -85,6 +97,28 @@ struct linux_binprm {
#define BINPRM_FLAGS_PRESERVE_ARGV0_BIT 3
#define BINPRM_FLAGS_PRESERVE_ARGV0 (1 << BINPRM_FLAGS_PRESERVE_ARGV0_BIT)
+/* binfmt_misc dispatched to the interpreter transparently */
+#define BINPRM_FLAGS_TRANSPARENT_INTERP_BIT 4
+#define BINPRM_FLAGS_TRANSPARENT_INTERP (1 << BINPRM_FLAGS_TRANSPARENT_INTERP_BIT)
+
+/**
+ * bprm_at_flags - the AT_FLAGS this invocation implies
+ * @bprm: binary that is being executed
+ *
+ * Tell the program on the receiving end which dispatch contract it got.
+ *
+ * Return: the AT_FLAGS value for this exec
+ */
+static inline unsigned long bprm_at_flags(const struct linux_binprm *bprm)
+{
+ /* Transparency preserves the whole argv, argv[0] included. */
+ if (bprm->interp_flags & BINPRM_FLAGS_TRANSPARENT_INTERP)
+ return AT_FLAGS_TRANSPARENT_INTERP;
+ if (bprm->interp_flags & BINPRM_FLAGS_PRESERVE_ARGV0)
+ return AT_FLAGS_PRESERVE_ARGV0;
+ return 0;
+}
+
/*
* This structure defines the functions that are used to load the binary formats that
* linux accepts.
@@ -101,8 +135,8 @@ struct linux_binfmt {
#if IS_ENABLED(CONFIG_BINFMT_MISC)
struct binfmt_misc {
- struct list_head entries;
- rwlock_t entries_lock;
+ struct hlist_head entries;
+ spinlock_t entries_lock;
bool enabled;
} __randomize_layout;
@@ -129,6 +163,8 @@ extern int begin_new_exec(struct linux_binprm * bprm);
extern void setup_new_exec(struct linux_binprm * bprm);
extern void finalize_exec(struct linux_binprm *bprm);
extern void would_dump(struct linux_binprm *, struct file *);
+struct file *bprm_open_interpreter(struct linux_binprm *bprm, const char *path);
+void bprm_drop_loader(struct linux_binprm *bprm);
extern int suid_dumpable;
diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
index 9c3be157397e..e38d9e60569f 100644
--- a/include/linux/user_namespace.h
+++ b/include/linux/user_namespace.h
@@ -58,6 +58,9 @@ enum ucount_type {
UCOUNT_FANOTIFY_GROUPS,
UCOUNT_FANOTIFY_MARKS,
#endif
+#if IS_ENABLED(CONFIG_BINFMT_MISC)
+ UCOUNT_BINFMT_MISC_INTERPRETERS,
+#endif
UCOUNT_COUNTS,
};
diff --git a/include/uapi/linux/binfmts.h b/include/uapi/linux/binfmts.h
index c6f9450efc12..aafc07d78b80 100644
--- a/include/uapi/linux/binfmts.h
+++ b/include/uapi/linux/binfmts.h
@@ -22,4 +22,11 @@ struct pt_regs;
#define AT_FLAGS_PRESERVE_ARGV0_BIT 0
#define AT_FLAGS_PRESERVE_ARGV0 (1 << AT_FLAGS_PRESERVE_ARGV0_BIT)
+/*
+ * The interpreter runs transparently: the argument vector and the exe
+ * link belong to the binary passed in AT_EXECFD.
+ */
+#define AT_FLAGS_TRANSPARENT_INTERP_BIT 1
+#define AT_FLAGS_TRANSPARENT_INTERP (1 << AT_FLAGS_TRANSPARENT_INTERP_BIT)
+
#endif /* _UAPI_LINUX_BINFMTS_H */
diff --git a/kernel/ucount.c b/kernel/ucount.c
index d6dc3e859f12..ec8b1445e287 100644
--- a/kernel/ucount.c
+++ b/kernel/ucount.c
@@ -4,6 +4,7 @@
#include <linux/sysctl.h>
#include <linux/slab.h>
#include <linux/cred.h>
+#include <linux/export.h>
#include <linux/hash.h>
#include <linux/kmemleak.h>
#include <linux/user_namespace.h>
@@ -89,6 +90,9 @@ static const struct ctl_table user_table[] = {
UCOUNT_ENTRY("max_fanotify_groups"),
UCOUNT_ENTRY("max_fanotify_marks"),
#endif
+#if IS_ENABLED(CONFIG_BINFMT_MISC)
+ UCOUNT_ENTRY("max_binfmt_misc_interpreters"),
+#endif
};
#endif /* CONFIG_SYSCTL */
@@ -233,6 +237,7 @@ fail:
put_ucounts(ucounts);
return NULL;
}
+EXPORT_SYMBOL_FOR_MODULES(inc_ucount, "binfmt_misc");
void dec_ucount(struct ucounts *ucounts, enum ucount_type type)
{
@@ -243,6 +248,7 @@ void dec_ucount(struct ucounts *ucounts, enum ucount_type type)
}
put_ucounts(ucounts);
}
+EXPORT_SYMBOL_FOR_MODULES(dec_ucount, "binfmt_misc");
long inc_rlimit_ucounts(struct ucounts *ucounts, enum rlimit_type type, long v)
{
diff --git a/kernel/user.c b/kernel/user.c
index 7aef4e679a6a..21bafdc11379 100644
--- a/kernel/user.c
+++ b/kernel/user.c
@@ -23,9 +23,9 @@
#if IS_ENABLED(CONFIG_BINFMT_MISC)
struct binfmt_misc init_binfmt_misc = {
- .entries = LIST_HEAD_INIT(init_binfmt_misc.entries),
+ .entries = HLIST_HEAD_INIT,
.enabled = true,
- .entries_lock = __RW_LOCK_UNLOCKED(init_binfmt_misc.entries_lock),
+ .entries_lock = __SPIN_LOCK_UNLOCKED(init_binfmt_misc.entries_lock),
};
EXPORT_SYMBOL_GPL(init_binfmt_misc);
#endif
diff --git a/tools/testing/selftests/exec/.gitignore b/tools/testing/selftests/exec/.gitignore
index 7f3d1ae762ec..e42ecd4c908d 100644
--- a/tools/testing/selftests/exec/.gitignore
+++ b/tools/testing/selftests/exec/.gitignore
@@ -19,3 +19,14 @@ null-argv
xxxxxxxx*
pipe
S_I*.test
+binfmt_misc_bpf
+binfmt_misc_interplimit
+binfmt_bpf_interp
+binfmt_bpf_app
+binfmt_misc_transparent
+binfmt_transparent_interp
+binfmt_misc_loader
+binfmt_loader_payload
+binfmt_loader_payload_static
+*.bpf.o
+vmlinux.h
diff --git a/tools/testing/selftests/exec/Makefile b/tools/testing/selftests/exec/Makefile
index 45a3cfc435cf..b640af8f02b5 100644
--- a/tools/testing/selftests/exec/Makefile
+++ b/tools/testing/selftests/exec/Makefile
@@ -21,9 +21,56 @@ TEST_GEN_PROGS += recursion-depth
TEST_GEN_PROGS += null-argv
TEST_GEN_PROGS += check-exec
+# binfmt_misc must not be reachable as an exec source or as a stacking layer,
+# or an 'F' entry can pin the instance that owns it. Unprivileged, no bpf.
+TEST_GEN_PROGS += binfmt_misc_selfpin
+
+# The interpreters an 'F' or 'B' entry pre-opens are charged against
+# UCOUNT_BINFMT_MISC_INTERPRETERS. Unprivileged, no bpf.
+TEST_GEN_PROGS += binfmt_misc_interplimit
+
+# 'D' (register disabled) binfmt_misc test: an entry that exists but does
+# not dispatch until it is enabled. Static magic entry, no bpf toolchain.
+TEST_GEN_PROGS += binfmt_misc_disabled
+
+# Static ('T' flag) transparent binfmt_misc test; the asserting interpreter
+# is shared with the bpf harness's transparent case. No bpf toolchain needed.
+TEST_GEN_PROGS += binfmt_misc_transparent
+TEST_GEN_FILES += binfmt_transparent_interp
+
+# 'L' (loader substitution) binfmt_misc test: the payload runs as the main
+# image with a copy of the system loader substituted for its PT_INTERP and
+# asserts the native identity from inside; the static build proves the
+# override is dropped for a binary without PT_INTERP.
+TEST_GEN_PROGS += binfmt_misc_loader
+TEST_GEN_FILES += binfmt_loader_payload binfmt_loader_payload_static
+
+# binfmt_misc bpf-backed ('B') handler test: a libbpf harness plus its
+# struct_ops objects and the test interpreter/app it routes between. Only
+# built when clang, bpftool, the vmlinux BTF and libbpf are all present
+# (HAVE_BPF_TOOLCHAIN=y forces it) so the other exec selftests don't grow
+# a bpf toolchain dependency.
+CLANG ?= clang
+BPFTOOL ?= bpftool
+VMLINUX_BTF ?= /sys/kernel/btf/vmlinux
+HAVE_BPF_TOOLCHAIN ?= $(shell command -v $(CLANG) >/dev/null 2>&1 && \
+ command -v $(BPFTOOL) >/dev/null 2>&1 && \
+ test -r $(VMLINUX_BTF) && \
+ pkg-config --exists libbpf 2>/dev/null && echo y)
+ifeq ($(HAVE_BPF_TOOLCHAIN),y)
+TEST_GEN_PROGS += binfmt_misc_bpf
+TEST_GEN_FILES += bpf_interp.bpf.o nix_origin.bpf.o transparent.bpf.o
+TEST_GEN_FILES += loader.bpf.o interp_bind.bpf.o
+TEST_GEN_FILES += binfmt_bpf_interp binfmt_bpf_app binfmt_bind_interp
+else
+$(info exec selftests: skipping binfmt_misc_bpf, needs clang, bpftool, vmlinux BTF and libbpf)
+endif
+
EXTRA_CLEAN := $(OUTPUT)/subdir.moved $(OUTPUT)/execveat.moved $(OUTPUT)/xxxxx* \
$(OUTPUT)/S_I*.test
+LOCAL_HDRS += binfmt_misc_common.h
+
include ../lib.mk
CHECK_EXEC_SAMPLES := $(top_srcdir)/samples/check-exec
@@ -55,3 +102,49 @@ $(OUTPUT)/script-exec.inc: $(CHECK_EXEC_SAMPLES)/script-exec.inc
cp $< $@
$(OUTPUT)/script-noexec.inc: $(CHECK_EXEC_SAMPLES)/script-noexec.inc
cp $< $@
+
+# Reuses setup_userns()/write_file() from the filesystems selftests. Their
+# wrappers.h wants the uapi headers, so ask for them here rather than widening
+# CFLAGS for every program in this directory.
+$(OUTPUT)/binfmt_misc_selfpin: CFLAGS += $(TOOLS_INCLUDES)
+$(OUTPUT)/binfmt_misc_selfpin: ../filesystems/utils.c
+$(OUTPUT)/binfmt_misc_interplimit: CFLAGS += $(TOOLS_INCLUDES)
+$(OUTPUT)/binfmt_misc_interplimit: ../filesystems/utils.c
+
+# --- binfmt_misc bpf ('B') handler test ---------------------------------
+# The struct_ops bpf objects are compiled against the running kernel's BTF.
+# CLANG/BPFTOOL/VMLINUX_BTF are set above next to the toolchain check;
+# override LIBBPF_CFLAGS/LDLIBS to point at a libbpf install.
+BPF_CFLAGS ?= -I$(OUTPUT)
+LIBBPF_CFLAGS ?=
+LIBBPF_LDLIBS ?= -lbpf -lelf -lz
+
+$(OUTPUT)/vmlinux.h:
+ $(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@
+
+# BPF_NO_KFUNC_PROTOTYPES: the programs declare the kfuncs they use themselves.
+$(OUTPUT)/%.bpf.o: %.bpf.c $(OUTPUT)/vmlinux.h
+ $(CLANG) -g -O2 -target bpf -mcpu=v3 -DBPF_NO_KFUNC_PROTOTYPES \
+ $(BPF_CFLAGS) $(LIBBPF_CFLAGS) -c $< -o $@
+
+$(OUTPUT)/binfmt_misc_bpf: binfmt_misc_bpf.c binfmt_misc_common.h
+ $(CC) $(CFLAGS) $(LIBBPF_CFLAGS) $(LDFLAGS) $< $(LIBBPF_LDLIBS) -o $@
+
+$(OUTPUT)/binfmt_bpf_interp: binfmt_bpf_interp.c
+ $(CC) $(CFLAGS) $(LDFLAGS) $< -o $@
+
+$(OUTPUT)/binfmt_bind_interp: binfmt_bind_interp.c
+ $(CC) $(CFLAGS) $(LDFLAGS) $< -o $@
+
+$(OUTPUT)/binfmt_loader_payload: binfmt_loader_payload.c binfmt_misc_common.h
+ $(CC) $(CFLAGS) $(LDFLAGS) -fPIE -pie $< -o $@
+
+$(OUTPUT)/binfmt_loader_payload_static: binfmt_loader_payload.c binfmt_misc_common.h
+ $(CC) $(CFLAGS) $(LDFLAGS) -static $< -o $@
+
+# PT_INTERP is set to the literal "$ORIGIN/binfmt_bpf_interp"; the nix_origin
+# handler resolves it relative to the binary at run time.
+$(OUTPUT)/binfmt_bpf_app: binfmt_bpf_app.c
+ $(CC) $(CFLAGS) $(LDFLAGS) -Wl,--dynamic-linker,'$$ORIGIN/binfmt_bpf_interp' $< -o $@
+
+EXTRA_CLEAN += $(OUTPUT)/vmlinux.h $(OUTPUT)/*.bpf.o
diff --git a/tools/testing/selftests/exec/binfmt_bind_interp.c b/tools/testing/selftests/exec/binfmt_bind_interp.c
new file mode 100644
index 000000000000..06d65062856b
--- /dev/null
+++ b/tools/testing/selftests/exec/binfmt_bind_interp.c
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test interpreter for the bound-interpreter case of the binfmt_misc_bpf
+ * selftest. Two copies are installed at different paths and bound to one
+ * entry under different names; printing argv[0] - the path the kernel ran
+ * this copy under - tells the harness which of them the load program picked.
+ */
+#include <stdio.h>
+
+int main(int argc, char **argv)
+{
+ printf("BIND_RAN %s\n", argc > 0 ? argv[0] : "");
+ return 0;
+}
diff --git a/tools/testing/selftests/exec/binfmt_bpf_app.c b/tools/testing/selftests/exec/binfmt_bpf_app.c
new file mode 100644
index 000000000000..472270f148bc
--- /dev/null
+++ b/tools/testing/selftests/exec/binfmt_bpf_app.c
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A relocatable binary for the binfmt_misc_bpf $ORIGIN case. The Makefile
+ * links it with PT_INTERP set to the literal "$ORIGIN/binfmt_bpf_interp"
+ * (-Wl,--dynamic-linker), which the kernel ELF loader cannot resolve. The
+ * nix_origin bpf handler resolves it relative to this binary's directory and
+ * routes execution to the co-located interpreter.
+ */
+int main(void)
+{
+ return 0;
+}
diff --git a/tools/testing/selftests/exec/binfmt_bpf_interp.c b/tools/testing/selftests/exec/binfmt_bpf_interp.c
new file mode 100644
index 000000000000..2db205f095b2
--- /dev/null
+++ b/tools/testing/selftests/exec/binfmt_bpf_interp.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test interpreter for the binfmt_misc_bpf selftest. A bpf-backed 'B' handler
+ * routes a matched binary here; printing this marker proves the program's
+ * chosen interpreter actually ran.
+ */
+#include <unistd.h>
+
+int main(int argc, char **argv)
+{
+ (void)argc;
+ (void)argv;
+ write(1, "BPF_INTERP_RAN\n", 15);
+ return 0;
+}
diff --git a/tools/testing/selftests/exec/binfmt_loader_payload.c b/tools/testing/selftests/exec/binfmt_loader_payload.c
new file mode 100644
index 000000000000..272db8efb4b5
--- /dev/null
+++ b/tools/testing/selftests/exec/binfmt_loader_payload.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Payload for the binfmt_misc 'L' (loader substitution) selftest. It is
+ * executed as the MAIN image - a fully native exec - with the registered
+ * interpreter substituted for its PT_INTERP, and asserts the native
+ * identity from the inside. Exits 0 when every surface checks out.
+ *
+ * Modes, selected by the orchestrator via the environment:
+ * - default: full assertions, path-based ones included
+ * - BINFMT_TEST_MEMFD=1: executed from an inaccessible memfd, skip
+ * the path-based assertions
+ * - BINFMT_TEST_STATIC=1: static build; the override was dropped, so
+ * expect no interpreter at all
+ */
+#define _GNU_SOURCE
+#include <elf.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/auxv.h>
+#include <unistd.h>
+
+#include "binfmt_misc_common.h"
+
+/* Start of our own mapped image, courtesy of the linker. */
+extern const char __ehdr_start[];
+
+/* An image is never this large; used to bracket "within our image". */
+#define IMAGE_SPAN (16UL << 20)
+
+static int failed;
+
+static void check(int cond, const char *what)
+{
+ if (cond)
+ return;
+ fprintf(stderr, "[payload] FAILED: %s (errno %d)\n", what, errno);
+ failed = 1;
+}
+
+/* Return whether /proc/self/maps names a path starting with @prefix. */
+static int maps_has_prefix(const char *prefix)
+{
+ char *line = NULL;
+ size_t len = 0;
+ int found = 0;
+ FILE *f;
+
+ f = fopen("/proc/self/maps", "r");
+ if (!f)
+ return -1;
+ while (getline(&line, &len, f) > 0) {
+ char *path = strchr(line, '/');
+
+ if (path && !strncmp(path, prefix, strlen(prefix))) {
+ found = 1;
+ break;
+ }
+ }
+ free(line);
+ fclose(f);
+ return found;
+}
+
+int main(int argc, char *argv[])
+{
+ const char *binary = getenv("BINFMT_TEST_BINARY");
+ const char *interp = getenv("BINFMT_TEST_INTERP");
+ int memfd_mode = getenv("BINFMT_TEST_MEMFD") != NULL;
+ int static_mode = getenv("BINFMT_TEST_STATIC") != NULL;
+ unsigned long self = (unsigned long)__ehdr_start;
+ unsigned long base = getauxval(AT_BASE);
+ unsigned long phdr = getauxval(AT_PHDR);
+ unsigned long entry = getauxval(AT_ENTRY);
+ unsigned long start_code, end_code;
+
+ /* The argument vector is exactly what the caller built. */
+ check(argc == 3 && !strcmp(argv[0], PAYLOAD_ARGV0) &&
+ !strcmp(argv[1], PAYLOAD_ARG1) && !strcmp(argv[2], PAYLOAD_ARG2),
+ "argv was rewritten");
+
+ /* Native from birth: no execfd, no dispatch marker. */
+ check(getauxval(AT_EXECFD) == 0, "AT_EXECFD present");
+ check(getauxval(AT_FLAGS) == 0, "AT_FLAGS not native");
+
+ if (static_mode) {
+ /* The override was dropped: no interpreter was loaded. */
+ check(base == 0, "AT_BASE set for a static payload");
+ } else {
+ /* A loader is mapped in the interpreter slot, not our image. */
+ check(base != 0, "AT_BASE missing");
+ check(base < self || base >= self + IMAGE_SPAN,
+ "AT_BASE inside our own image");
+ }
+
+ /* We occupy the main-image slot. */
+ check(phdr >= self && phdr < self + IMAGE_SPAN,
+ "AT_PHDR outside our image");
+ check(entry >= self && entry < self + IMAGE_SPAN,
+ "AT_ENTRY outside our image");
+
+ /* The code statistics markers describe our image, natively placed. */
+ if (stat_codes(getpid(), &start_code, &end_code) == 0) {
+ check(start_code >= self && start_code < end_code &&
+ end_code < self + IMAGE_SPAN,
+ "stat start_code/end_code not our image");
+ check(entry >= start_code && entry < end_code,
+ "AT_ENTRY outside [start_code, end_code)");
+ } else {
+ check(0, "cannot parse /proc/self/stat");
+ }
+
+ if (!memfd_mode && binary) {
+ const char *execfn = (const char *)getauxval(AT_EXECFN);
+ const char *base_name = strrchr(binary, '/');
+
+ base_name = base_name ? base_name + 1 : binary;
+
+ /* exe link, AT_EXECFN and comm all follow the binary. */
+ check(exe_is(binary), "/proc/self/exe");
+ check(execfn && !strcmp(execfn, binary), "AT_EXECFN");
+ check(comm_is(base_name), "comm");
+
+ /* The running binary is write-denied, natively. */
+ check(write_denied(binary), "no ETXTBSY on the binary");
+ }
+
+ if (interp) {
+ int found = maps_has_prefix(interp);
+
+ if (static_mode)
+ /* Nothing was substituted, nothing may be mapped. */
+ check(found == 0, "loader mapped for a static payload");
+ else
+ /* The substituted loader shows under its real path. */
+ check(found == 1, "loader path not in /proc/self/maps");
+ }
+
+ if (failed)
+ return 1;
+ printf("[payload] native identity checks out\n");
+ return 0;
+}
diff --git a/tools/testing/selftests/exec/binfmt_misc_bpf.c b/tools/testing/selftests/exec/binfmt_misc_bpf.c
new file mode 100644
index 000000000000..b2a4518901b0
--- /dev/null
+++ b/tools/testing/selftests/exec/binfmt_misc_bpf.c
@@ -0,0 +1,638 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Selftest for binfmt_misc bpf-backed ('B') handlers.
+ *
+ * A handler is a struct binfmt_misc_ops struct_ops map with a sleepable match
+ * and a sleepable load program. Attaching it publishes it by name in the
+ * caller's user namespace; a 'B' entry referencing it by name in the
+ * interpreter field activates it:
+ *
+ * echo ':name:B::::<handler>:' > /proc/sys/fs/binfmt_misc/register
+ *
+ * Five self-contained cases are exercised:
+ *
+ * 1. bpf_interp: the match program matches a synthetic aarch64 ELF header
+ * from the prefetched bprm->buf and the load program routes it to a
+ * fixed interpreter of its choosing.
+ * 2. nix_origin: the match program reads the binary's program headers to
+ * commit only to a "$ORIGIN/..."-relative PT_INTERP and the load program
+ * resolves it to an interpreter co-located with the binary (the
+ * relocatable-loader case the kernel ELF loader cannot express).
+ * 3. transparent: the load program sets BPF_BINPRM_TRANSPARENT; the
+ * asserting interpreter (binfmt_transparent_interp) verifies the
+ * identity the kernel constructed (exe link, argv, cmdline, comm,
+ * AT_EXECFD, write denial) from inside the process.
+ * 4. loader: the load program sets BPF_BINPRM_LOADER; the payload
+ * (binfmt_loader_payload) runs as the main image with the selected
+ * interpreter substituted for its PT_INTERP and asserts the native
+ * identity from inside.
+ * 5. interp_bind: an entry registered disabled with 'D' is given its
+ * interpreters one write at a time, and the load program picks one by
+ * name per exec. Replacing what the path holds afterwards changes
+ * nothing, which is the point of binding a file rather than resolving
+ * a name at exec time. Enabling the entry seals it.
+ *
+ * The first two route to a test interpreter that prints BPF_INTERP_RAN,
+ * proving the program's chosen interpreter actually ran.
+ */
+#define _GNU_SOURCE
+#include <elf.h>
+#include <limits.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <bpf/btf.h>
+#include <bpf/libbpf.h>
+
+#include "binfmt_misc_common.h"
+#include "kselftest_harness.h"
+
+#define INTERP_PATH "/tmp/binfmt_bpf_interp"
+#define AARCH64_PATH "/tmp/binfmt_bpf_aarch64"
+#define RELOC_TEMPLATE "/tmp/binfmt_relocXXXXXX"
+#define TRANS_INTERP "/tmp/binfmt_transparent_interp"
+#define TRANS_PATH "/tmp/binfmt_bpf_riscv"
+#define EXPECT "BPF_INTERP_RAN"
+#define TRANS_EXPECT "TRANSPARENT_OK"
+#define LOADER_INTERP "/tmp/binfmt_loader_interp"
+#define LOADER_PATH "/tmp/binfmt_bpf_loader.ldrtest"
+#define BIND_FIRST "/tmp/binfmt_bind_first"
+#define BIND_SECOND "/tmp/binfmt_bind_second"
+#define BIND_ARM_PATH "/tmp/binfmt_bind_arm"
+#define BIND_RISCV_PATH "/tmp/binfmt_bind_riscv"
+#define BIND_EXPECT "BIND_RAN "
+#define BIND_MAX 100
+#define INTERP_LIMIT "/proc/sys/user/max_binfmt_misc_interpreters"
+/* Exit status of the binding child when it cannot set up a budget of its own. */
+#define BIND_NO_BUDGET 200
+
+/* A minimal 64-bit little-endian ELF header, padded to the read size. */
+static int create_fake_elf(const char *path, unsigned short machine)
+{
+ unsigned char hdr[256] = {0};
+ int fd;
+
+ hdr[0] = 0x7f; hdr[1] = 'E'; hdr[2] = 'L'; hdr[3] = 'F';
+ hdr[4] = ELFCLASS64;
+ hdr[5] = ELFDATA2LSB;
+ hdr[6] = EV_CURRENT;
+ hdr[16] = ET_EXEC;
+ hdr[18] = machine & 0xff; /* e_machine, little-endian */
+ hdr[19] = machine >> 8;
+ hdr[20] = EV_CURRENT;
+
+ unlink(path);
+ fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0755);
+ if (fd < 0)
+ return -1;
+ if (write(fd, hdr, sizeof(hdr)) != (ssize_t)sizeof(hdr)) {
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ return 0;
+}
+
+/*
+ * Register a 'B' entry for @handler. With @flags "D" the entry is created
+ * disabled, which is what leaves it open to being given interpreters.
+ */
+static int register_entry(const char *name, const char *handler,
+ const char *flags)
+{
+ char rule[PATH_MAX];
+
+ snprintf(rule, sizeof(rule), ":%s:B::::%s:%s", name, handler,
+ flags ? flags : "");
+ return write_reg(rule);
+}
+
+static int check_output(const char *cmd, const char *expected)
+{
+ char buf[128];
+ FILE *fp;
+
+ fp = popen(cmd, "r");
+ if (!fp)
+ return -1;
+ if (!fgets(buf, sizeof(buf), fp)) {
+ pclose(fp);
+ return -1;
+ }
+ pclose(fp);
+ return strncmp(buf, expected, strlen(expected)) ? -1 : 0;
+}
+
+/* Does the kernel BTF know struct binfmt_misc_ops (CONFIG_BINFMT_MISC_BPF)? */
+static bool have_binfmt_misc_ops(void)
+{
+ struct btf *btf = btf__load_vmlinux_btf();
+ bool have;
+
+ have = btf && btf__find_by_name_kind(btf, "binfmt_misc_ops",
+ BTF_KIND_STRUCT) >= 0;
+ btf__free(btf);
+ return have;
+}
+
+/* The reason bpf handler cases cannot run here, NULL if they can. */
+static const char *bpf_handler_unsupported(void)
+{
+ if (getuid() != 0)
+ return "test must be run as root";
+ if (!have_binfmt_misc_ops())
+ return "no struct binfmt_misc_ops in the kernel BTF (CONFIG_BINFMT_MISC_BPF)";
+ if (!binfmt_misc_available())
+ return "no binfmt_misc";
+ return NULL;
+}
+
+/* An attached handler with its 'B' entry activated. */
+struct bpf_case {
+ struct bpf_object *obj;
+ struct bpf_link *link;
+ const char *entry;
+};
+
+/*
+ * Load @objfile, attach its struct_ops map @handler (which publishes the
+ * handler) and register a 'B' entry named @entry that references it, with
+ * @flags as the entry's register-string flags.
+ */
+static int bpf_case_start_flags(struct bpf_case *c, const char *objfile,
+ const char *handler, const char *entry,
+ const char *flags)
+{
+ struct bpf_map *map;
+
+ c->obj = NULL;
+ c->link = NULL;
+ c->entry = entry;
+
+ c->obj = bpf_object__open_file(objfile, NULL);
+ if (!c->obj || libbpf_get_error(c->obj)) {
+ fprintf(stderr, "open %s failed\n", objfile);
+ c->obj = NULL;
+ return -1;
+ }
+ if (bpf_object__load(c->obj)) {
+ fprintf(stderr, "load %s failed (check dmesg for the verifier log)\n",
+ objfile);
+ goto fail;
+ }
+ map = bpf_object__find_map_by_name(c->obj, handler);
+ if (!map) {
+ fprintf(stderr, "no struct_ops map '%s' in %s\n", handler, objfile);
+ goto fail;
+ }
+ c->link = bpf_map__attach_struct_ops(map);
+ if (!c->link || libbpf_get_error(c->link)) {
+ fprintf(stderr, "attach struct_ops '%s' failed\n", handler);
+ c->link = NULL;
+ goto fail;
+ }
+ if (register_entry(entry, handler, flags)) {
+ fprintf(stderr, "register 'B' entry '%s' failed\n", entry);
+ goto fail;
+ }
+ return 0;
+
+fail:
+ bpf_link__destroy(c->link);
+ bpf_object__close(c->obj);
+ c->obj = NULL;
+ c->link = NULL;
+ return -1;
+}
+
+static int bpf_case_start(struct bpf_case *c, const char *objfile,
+ const char *handler, const char *entry)
+{
+ return bpf_case_start_flags(c, objfile, handler, entry, NULL);
+}
+
+static void bpf_case_stop(struct bpf_case *c)
+{
+ unregister(c->entry);
+ bpf_link__destroy(c->link);
+ bpf_object__close(c->obj);
+}
+
+/* Activate @handler, run @target and check it produced @expect. */
+static int run_case(const char *objfile, const char *handler,
+ const char *entry, const char *target, const char *expect)
+{
+ struct bpf_case c;
+ int ret;
+
+ if (bpf_case_start(&c, objfile, handler, entry))
+ return -1;
+ ret = check_output(target, expect);
+ bpf_case_stop(&c);
+ return ret;
+}
+
+FIXTURE(bpf_handler) {
+ char obj[PATH_MAX]; /* struct_ops object of the case under test */
+};
+
+FIXTURE_SETUP(bpf_handler)
+{
+ char src[PATH_MAX];
+ const char *why = bpf_handler_unsupported();
+
+ if (why)
+ SKIP(return, "%s", why);
+
+ /* Shared test interpreter. */
+ ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_bpf_interp"), 0);
+ ASSERT_EQ(copy_file(src, INTERP_PATH), 0);
+}
+
+FIXTURE_TEARDOWN(bpf_handler)
+{
+ unlink(INTERP_PATH);
+}
+
+/* The match program matches a synthetic header, the load program routes it. */
+TEST_F(bpf_handler, fixed_interpreter)
+{
+ ASSERT_EQ(create_fake_elf(AARCH64_PATH, EM_AARCH64), 0);
+ ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
+ "bpf_interp.bpf.o"), 0);
+ EXPECT_EQ(run_case(self->obj, "bpf_interp", "test_bpf_interp",
+ AARCH64_PATH, EXPECT), 0);
+ unlink(AARCH64_PATH);
+}
+
+/* A "$ORIGIN/..." PT_INTERP resolved to an interpreter next to the binary. */
+TEST_F(bpf_handler, origin_relative_interpreter)
+{
+ char src[PATH_MAX], app[PATH_MAX], interp[PATH_MAX];
+ char dir[] = RELOC_TEMPLATE;
+
+ ASSERT_NE(mkdtemp(dir), NULL);
+ snprintf(app, sizeof(app), "%s/app", dir);
+ snprintf(interp, sizeof(interp), "%s/binfmt_bpf_interp", dir);
+ ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_bpf_app"), 0);
+ ASSERT_EQ(copy_file(src, app), 0);
+ ASSERT_EQ(copy_file(INTERP_PATH, interp), 0);
+
+ ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
+ "nix_origin.bpf.o"), 0);
+ EXPECT_EQ(run_case(self->obj, "nix_origin", "test_bpf_origin",
+ app, EXPECT), 0);
+
+ unlink(app);
+ unlink(interp);
+ rmdir(dir);
+}
+
+/* A transparent dispatch: the process presents as the binary, not the interp. */
+TEST_F(bpf_handler, transparent_dispatch)
+{
+ char src[PATH_MAX], cmd[PATH_MAX + 16];
+
+ /* Probe for transparent-mode support via its static counterpart. */
+ if (!binfmt_flag_supported('T'))
+ SKIP(return, "kernel without transparent mode");
+
+ ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_transparent_interp"), 0);
+ ASSERT_EQ(copy_file(src, TRANS_INTERP), 0);
+ ASSERT_EQ(create_fake_elf(TRANS_PATH, EM_RISCV), 0);
+
+ setenv("BINFMT_TEST_BINARY", TRANS_PATH, 1);
+ snprintf(cmd, sizeof(cmd), "%s argone argtwo", TRANS_PATH);
+ ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
+ "transparent.bpf.o"), 0);
+ EXPECT_EQ(run_case(self->obj, "transparent", "test_bpf_transparent",
+ cmd, TRANS_EXPECT), 0);
+
+ unlink(TRANS_PATH);
+ unlink(TRANS_INTERP);
+}
+
+/* A per-exec loader substitution: the payload runs as a native exec. */
+TEST_F(bpf_handler, loader_substitution)
+{
+ char src[PATH_MAX], loader[PATH_MAX];
+ struct bpf_case c;
+ int status;
+
+ if (find_loader(loader, sizeof(loader)))
+ SKIP(return, "cannot determine own PT_INTERP");
+
+ ASSERT_EQ(copy_file(loader, LOADER_INTERP), 0);
+ ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_loader_payload"), 0);
+ ASSERT_EQ(copy_file(src, LOADER_PATH), 0);
+ ASSERT_EQ(patch_file(LOADER_PATH, EI_PAD, LOADER_MARKER,
+ strlen(LOADER_MARKER)), 0);
+ ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
+ "loader.bpf.o"), 0);
+
+ setenv("BINFMT_TEST_BINARY", LOADER_PATH, 1);
+ setenv("BINFMT_TEST_INTERP", LOADER_INTERP, 1);
+
+ ASSERT_EQ(bpf_case_start(&c, self->obj, "loader", "test_bpf_loader"), 0);
+ status = run_payload(LOADER_PATH);
+ bpf_case_stop(&c);
+ EXPECT_EQ(status, 0);
+
+ unsetenv("BINFMT_TEST_INTERP");
+ unlink(LOADER_PATH);
+ unlink(LOADER_INTERP);
+}
+
+/* The errno an exec of @path fails with, 0 if it succeeded. */
+static int exec_errno(const char *path)
+{
+ int status;
+ pid_t pid;
+
+ pid = fork();
+ if (pid == 0) {
+ execl(path, path, (char *)NULL);
+ _exit(errno);
+ }
+ if (pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status))
+ return -1;
+ return WEXITSTATUS(status);
+}
+
+/* Install a copy of the bound-interpreter test binary at @path. */
+static int install_interp(const char *path)
+{
+ char src[PATH_MAX];
+
+ if (artifact_path(src, sizeof(src), "binfmt_bind_interp"))
+ return -1;
+ return copy_file(src, path);
+}
+
+/* Bind @path to @entry under @name, the '+' command of a disabled entry. */
+static int entry_bind(const char *entry, const char *name, const char *path)
+{
+ char cmd[PATH_MAX];
+
+ snprintf(cmd, sizeof(cmd), "+%s %s\n", name, path);
+ return entry_command(entry, cmd);
+}
+
+/* Set the interpreter budget of this namespace. */
+static int write_interp_limit(const char *val)
+{
+ ssize_t n;
+ int fd;
+
+ fd = open(INTERP_LIMIT, O_WRONLY | O_CLOEXEC);
+ if (fd < 0)
+ return -1;
+ n = write(fd, val, strlen(val));
+ close(fd);
+ return n < 0 ? -1 : 0;
+}
+
+/*
+ * The errno a bind is refused with when the writer is a child that has spent
+ * the budget of a user namespace of its own, 0 if it succeeded and -1 if the
+ * child could not set itself up. The fd is opened here and inherited, so the
+ * interpreter is still opened with this process's credentials.
+ */
+static int bind_out_of_budget(const char *entry, const char *name,
+ const char *path)
+{
+ char cmd[PATH_MAX], file[PATH_MAX];
+ int fd, status, retval;
+ pid_t pid;
+
+ snprintf(file, sizeof(file), BINFMT_DIR "/%s", entry);
+ snprintf(cmd, sizeof(cmd), "+%s %s\n", name, path);
+
+ fd = open(file, O_WRONLY | O_CLOEXEC);
+ if (fd < 0)
+ return -1;
+
+ pid = fork();
+ if (pid == 0) {
+ ssize_t n;
+
+ /* A namespace of its own, with nothing left in it to spend. */
+ if (unshare(CLONE_NEWUSER) || write_interp_limit("0"))
+ _exit(BIND_NO_BUDGET);
+ n = write(fd, cmd, strlen(cmd));
+ _exit(n < 0 ? errno : 0);
+ }
+ close(fd);
+ if (pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status))
+ return -1;
+ retval = WEXITSTATUS(status);
+ return retval == BIND_NO_BUDGET ? -1 : retval;
+}
+
+FIXTURE(bound_interp) {
+ char obj[PATH_MAX];
+ struct bpf_case c;
+ bool started;
+};
+
+FIXTURE_SETUP(bound_interp)
+{
+ const char *why = bpf_handler_unsupported();
+
+ if (why)
+ SKIP(return, "%s", why);
+ if (!binfmt_flag_supported('D')) {
+ ASSERT_EQ(errno, EINVAL);
+ SKIP(return, "kernel without the 'D' flag");
+ }
+
+ ASSERT_EQ(install_interp(BIND_FIRST), 0);
+ ASSERT_EQ(install_interp(BIND_SECOND), 0);
+
+ ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
+ "interp_bind.bpf.o"), 0);
+
+ /*
+ * Registered disabled, so it cannot be matched yet and can still be
+ * given interpreters. Each path is resolved once, by its write(2);
+ * from here on the entry holds the files themselves.
+ */
+ ASSERT_EQ(bpf_case_start_flags(&self->c, self->obj, "interp_bind",
+ "test_interp_bind", "D"), 0);
+ self->started = true;
+
+ ASSERT_EQ(entry_bind("test_interp_bind", "first", BIND_FIRST), 0);
+ ASSERT_EQ(entry_bind("test_interp_bind", "second", BIND_SECOND), 0);
+}
+
+FIXTURE_TEARDOWN(bound_interp)
+{
+ if (self->started)
+ bpf_case_stop(&self->c);
+ unlink(BIND_FIRST);
+ unlink(BIND_SECOND);
+ unlink(AARCH64_PATH);
+ unlink(BIND_RISCV_PATH);
+ unlink(BIND_ARM_PATH);
+}
+
+/* Enabling is what makes the configured entry matchable. */
+static int activate(const char *entry)
+{
+ return entry_command(entry, "1\n");
+}
+
+/* One entry, one interpreter per guest architecture, picked per exec. */
+TEST_F(bound_interp, selects_by_name)
+{
+ ASSERT_EQ(create_fake_elf(AARCH64_PATH, EM_AARCH64), 0);
+ ASSERT_EQ(create_fake_elf(BIND_RISCV_PATH, EM_RISCV), 0);
+
+ /* Disabled, so it does not match and no format claims the binary. */
+ EXPECT_EQ(exec_errno(AARCH64_PATH), ENOEXEC);
+
+ ASSERT_EQ(activate("test_interp_bind"), 0);
+ EXPECT_EQ(check_output(AARCH64_PATH, BIND_EXPECT BIND_FIRST), 0);
+ EXPECT_EQ(check_output(BIND_RISCV_PATH, BIND_EXPECT BIND_SECOND), 0);
+}
+
+/* What was bound is what runs, whatever the path holds afterwards. */
+TEST_F(bound_interp, path_no_longer_decides)
+{
+ char other[PATH_MAX];
+
+ ASSERT_EQ(create_fake_elf(AARCH64_PATH, EM_AARCH64), 0);
+ ASSERT_EQ(activate("test_interp_bind"), 0);
+
+ /* Bound interpreters are pinned against writes, exactly like 'F'. */
+ EXPECT_TRUE(write_denied(BIND_FIRST));
+
+ /* Replace the path with a different binary: a new file, new inode. */
+ ASSERT_EQ(artifact_path(other, sizeof(other), "binfmt_bpf_interp"), 0);
+ ASSERT_EQ(unlink(BIND_FIRST), 0);
+ ASSERT_EQ(copy_file(other, BIND_FIRST), 0);
+
+ EXPECT_EQ(check_output(AARCH64_PATH, BIND_EXPECT BIND_FIRST), 0);
+}
+
+/* The entry reports what it bound, under the names it bound them as. */
+TEST_F(bound_interp, entry_reports_bindings)
+{
+ EXPECT_TRUE(entry_shows("test_interp_bind",
+ "bpf-interpreter first " BIND_FIRST));
+ EXPECT_TRUE(entry_shows("test_interp_bind",
+ "bpf-interpreter second " BIND_SECOND));
+}
+
+/* Selecting a name the entry did not bind fails the exec. */
+TEST_F(bound_interp, unbound_name_fails)
+{
+ ASSERT_EQ(create_fake_elf(BIND_ARM_PATH, EM_ARM), 0);
+ ASSERT_EQ(activate("test_interp_bind"), 0);
+
+ EXPECT_EQ(exec_errno(BIND_ARM_PATH), ENOENT);
+}
+
+/* Activating seals it: what can be matched cannot be changed. */
+TEST_F(bound_interp, sealed_once_active)
+{
+ ASSERT_EQ(activate("test_interp_bind"), 0);
+
+ EXPECT_EQ(entry_bind("test_interp_bind", "third", BIND_SECOND), -EBUSY);
+ EXPECT_FALSE(entry_shows("test_interp_bind",
+ "bpf-interpreter third " BIND_SECOND));
+}
+
+/* The seal is for good: disabling the entry again reopens nothing. */
+TEST_F(bound_interp, disable_does_not_unseal)
+{
+ ASSERT_EQ(activate("test_interp_bind"), 0);
+ ASSERT_EQ(entry_command("test_interp_bind", "0\n"), 0);
+
+ EXPECT_EQ(entry_bind("test_interp_bind", "third", BIND_SECOND), -EBUSY);
+}
+
+/* An entry registered without 'D' is sealed from the start. */
+TEST_F(bound_interp, born_sealed)
+{
+ /* A second entry for the handler the fixture already published. */
+ ASSERT_EQ(register_entry("test_born_sealed", "interp_bind", NULL), 0);
+
+ EXPECT_EQ(entry_bind("test_born_sealed", "first", BIND_FIRST), -EBUSY);
+ unregister("test_born_sealed");
+}
+
+/* A name is bound once; a second use of it is refused. */
+TEST_F(bound_interp, duplicate_name_refused)
+{
+ EXPECT_EQ(entry_bind("test_interp_bind", "first", BIND_SECOND), -EEXIST);
+}
+
+/* A name is a printable word: the entry file reports 'name path' lines. */
+TEST_F(bound_interp, name_must_be_printable)
+{
+ /* A control character would forge a line into the entry file. */
+ EXPECT_EQ(entry_bind("test_interp_bind", "a\tb", BIND_FIRST), -EINVAL);
+ EXPECT_EQ(entry_bind("test_interp_bind", "a\nb", BIND_FIRST), -EINVAL);
+
+ /* A space cannot even be spelled: the path starts after the first one. */
+ EXPECT_EQ(entry_bind("test_interp_bind", "a b", BIND_FIRST), -EINVAL);
+}
+
+/* The command ends at the write: bytes past an embedded nul are refused. */
+TEST_F(bound_interp, trailing_bytes_refused)
+{
+ char cmd[PATH_MAX];
+ size_t len;
+ int fd;
+
+ /* entry_command() cannot spell a nul, so write the buffer raw. */
+ snprintf(cmd, sizeof(cmd), "+nul %s", BIND_FIRST);
+ len = strlen(cmd) + 1;
+ memcpy(cmd + len, "junk", sizeof("junk"));
+ len += sizeof("junk");
+
+ fd = open(BINFMT_DIR "/test_interp_bind", O_WRONLY | O_CLOEXEC);
+ ASSERT_GE(fd, 0);
+ EXPECT_EQ(write(fd, cmd, len), -1);
+ EXPECT_EQ(errno, EINVAL);
+ close(fd);
+
+ EXPECT_FALSE(entry_shows("test_interp_bind",
+ "bpf-interpreter nul " BIND_FIRST));
+}
+
+/* An entry binds at most BIND_MAX interpreters. */
+TEST_F(bound_interp, capped_bindings)
+{
+ char name[16];
+ int i;
+
+ /* The fixture bound "first" and "second" already. */
+ for (i = 2; i < BIND_MAX; i++) {
+ snprintf(name, sizeof(name), "n%d", i);
+ ASSERT_EQ(entry_bind("test_interp_bind", name, BIND_FIRST), 0);
+ }
+ EXPECT_EQ(entry_bind("test_interp_bind", "over", BIND_FIRST), -ENOSPC);
+}
+
+/* A binding pins a file: it is charged, and refused once the budget is out. */
+TEST_F(bound_interp, bindings_are_charged)
+{
+ int err = bind_out_of_budget("test_interp_bind", "third", BIND_FIRST);
+
+ if (err < 0)
+ SKIP(return, "no user namespaces or no " INTERP_LIMIT);
+
+ /* The charge follows the writer, not the entry file it writes to. */
+ EXPECT_EQ(err, ENOSPC);
+
+ /* The budget was the only thing in the way. */
+ EXPECT_EQ(entry_bind("test_interp_bind", "third", BIND_FIRST), 0);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/exec/binfmt_misc_common.h b/tools/testing/selftests/exec/binfmt_misc_common.h
new file mode 100644
index 000000000000..745aff84dc78
--- /dev/null
+++ b/tools/testing/selftests/exec/binfmt_misc_common.h
@@ -0,0 +1,315 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Helpers shared by the binfmt_misc selftests. */
+#ifndef __SELFTESTS_EXEC_BINFMT_MISC_COMMON_H
+#define __SELFTESTS_EXEC_BINFMT_MISC_COMMON_H
+
+#include <elf.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <link.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define BINFMT_DIR "/proc/sys/fs/binfmt_misc"
+#define BINFMT_REG BINFMT_DIR "/register"
+
+/* comm holds 15 usable chars; a read of /proc/self/comm appends a newline. */
+#define TASK_COMM_LEN 16
+
+/* The canonical payload argv: run_payload() passes it, the payloads assert it. */
+#define PAYLOAD_ARGV0 "payload-argv0"
+#define PAYLOAD_ARG1 "argone"
+#define PAYLOAD_ARG2 "argtwo"
+
+/* Marker the loader tests poke into the payload's e_ident padding. */
+#define LOADER_MARKER "LDRTST"
+
+/* Exit status run_payload() reports when the exec was refused as unhandled. */
+#define RUN_ENOEXEC 42
+
+static inline int copy_file(const char *src, const char *dst)
+{
+ char buf[4096];
+ int in, out;
+ ssize_t n;
+
+ in = open(src, O_RDONLY);
+ if (in < 0)
+ return -1;
+ /* The tests share /tmp, so never write through a name they don't own. */
+ unlink(dst);
+ out = open(dst, O_WRONLY | O_CREAT | O_EXCL, 0755);
+ if (out < 0) {
+ close(in);
+ return -1;
+ }
+ while ((n = read(in, buf, sizeof(buf))) > 0) {
+ if (write(out, buf, n) != n) {
+ close(in);
+ close(out);
+ return -1;
+ }
+ }
+ close(in);
+ close(out);
+ return n < 0 ? -1 : 0;
+}
+
+/* Write @rule to the register file, preserving the write's errno. */
+static inline int write_reg(const char *rule)
+{
+ int fd, saved;
+ ssize_t n;
+
+ fd = open(BINFMT_REG, O_WRONLY);
+ if (fd < 0)
+ return -1;
+ n = write(fd, rule, strlen(rule));
+ saved = errno;
+ close(fd);
+ errno = saved;
+ return n < 0 ? -1 : 0;
+}
+
+static inline void unregister(const char *name)
+{
+ char path[PATH_MAX];
+ int fd;
+
+ snprintf(path, sizeof(path), BINFMT_DIR "/%s", name);
+ fd = open(path, O_WRONLY);
+ if (fd >= 0) {
+ if (write(fd, "-1", 2) < 0)
+ ; /* best effort */
+ close(fd);
+ }
+}
+
+/* Write @line to @entry's file, reporting the errno it was refused with. */
+static inline int entry_command(const char *entry, const char *line)
+{
+ char path[PATH_MAX];
+ int fd, retval = 0;
+ size_t len = strlen(line);
+
+ snprintf(path, sizeof(path), BINFMT_DIR "/%s", entry);
+ fd = open(path, O_WRONLY | O_CLOEXEC);
+ if (fd < 0)
+ return -errno;
+ if (write(fd, line, len) != (ssize_t)len)
+ retval = -errno;
+ close(fd);
+ return retval;
+}
+
+/* Does @entry's file report @line? */
+static inline bool entry_shows(const char *entry, const char *line)
+{
+ char path[PATH_MAX], buf[PATH_MAX];
+ bool found = false;
+ FILE *fp;
+
+ snprintf(path, sizeof(path), BINFMT_DIR "/%s", entry);
+ fp = fopen(path, "r");
+ if (!fp)
+ return false;
+ while (fgets(buf, sizeof(buf), fp)) {
+ buf[strcspn(buf, "\n")] = '\0';
+ if (!strcmp(buf, line)) {
+ found = true;
+ break;
+ }
+ }
+ fclose(fp);
+ return found;
+}
+
+/* Mount binfmt_misc unless it already is, and report whether it is usable. */
+static inline bool binfmt_misc_available(void)
+{
+ if (access(BINFMT_REG, F_OK) < 0)
+ mount("binfmt_misc", BINFMT_DIR, "binfmt_misc", 0, NULL);
+ return access(BINFMT_REG, F_OK) == 0;
+}
+
+/* Absolute path of @name in the directory this test was built into. */
+static inline int artifact_path(char *out, size_t sz, const char *name)
+{
+ char exe[PATH_MAX];
+ ssize_t n;
+
+ n = readlink("/proc/self/exe", exe, sizeof(exe) - 1);
+ if (n < 0)
+ return -1;
+ exe[n] = '\0';
+ if ((size_t)snprintf(out, sz, "%s/%s", dirname(exe), name) >= sz)
+ return -1;
+ return 0;
+}
+
+/* Probe kernel support for a registration flag with a throwaway entry. */
+static inline bool binfmt_flag_supported(char flag)
+{
+ char rule[64];
+
+ snprintf(rule, sizeof(rule), ":bm_flag_probe:E::bmprobe::/bin/true:%c",
+ flag);
+ if (write_reg(rule))
+ return false;
+ unregister("bm_flag_probe");
+ return true;
+}
+
+/*
+ * Run @path with the canonical payload argv and return its exit status, or
+ * RUN_ENOEXEC when the exec itself was refused as unhandled.
+ */
+static inline int run_payload(const char *path)
+{
+ int status;
+ pid_t pid;
+
+ pid = fork();
+ if (pid == 0) {
+ execl(path, PAYLOAD_ARGV0, PAYLOAD_ARG1, PAYLOAD_ARG2,
+ (char *)NULL);
+ _exit(errno == ENOEXEC ? RUN_ENOEXEC : 126);
+ }
+ if (pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status))
+ return -1;
+ return WEXITSTATUS(status);
+}
+
+/* Does the exe link name @path? */
+static inline bool exe_is(const char *path)
+{
+ char exe[PATH_MAX], real[PATH_MAX];
+ ssize_t n;
+
+ n = readlink("/proc/self/exe", exe, sizeof(exe) - 1);
+ if (n <= 0 || !realpath(path, real))
+ return false;
+ exe[n] = '\0';
+ return !strcmp(exe, real);
+}
+
+/* Is comm @name truncated to what a comm can hold? */
+static inline bool comm_is(const char *name)
+{
+ char comm[TASK_COMM_LEN + 2], expect[TASK_COMM_LEN];
+ ssize_t n;
+ int fd;
+
+ fd = open("/proc/self/comm", O_RDONLY);
+ if (fd < 0)
+ return false;
+ n = read(fd, comm, sizeof(comm) - 1);
+ close(fd);
+ if (n <= 0)
+ return false;
+ if (comm[n - 1] == '\n')
+ n--;
+ comm[n] = '\0';
+ snprintf(expect, sizeof(expect), "%s", name);
+ return !strcmp(comm, expect);
+}
+
+/* Opening @path for writing has to fail with ETXTBSY. */
+static inline bool write_denied(const char *path)
+{
+ int fd = open(path, O_WRONLY);
+
+ if (fd >= 0) {
+ close(fd);
+ return false;
+ }
+ return errno == ETXTBSY;
+}
+
+static inline int patch_file(const char *path, off_t off, const void *data, size_t len)
+{
+ ssize_t n;
+ int fd;
+
+ fd = open(path, O_WRONLY);
+ if (fd < 0)
+ return -1;
+ n = pwrite(fd, data, len, off);
+ close(fd);
+ return n == (ssize_t)len ? 0 : -1;
+}
+
+/* start_code and end_code are the 26th and 27th fields of /proc/pid/stat. */
+static inline int stat_codes(pid_t pid, unsigned long *start_code,
+ unsigned long *end_code)
+{
+ char buf[4096], path[64], *p;
+ ssize_t n;
+ int fd, i;
+
+ snprintf(path, sizeof(path), "/proc/%d/stat", pid);
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return -1;
+ n = read(fd, buf, sizeof(buf) - 1);
+ close(fd);
+ if (n <= 0)
+ return -1;
+ buf[n] = '\0';
+
+ /* Skip "pid (comm)", then start_code is the 24th field after it. */
+ p = strrchr(buf, ')');
+ if (!p)
+ return -1;
+ p++;
+ for (i = 0; i < 23; i++) {
+ p = strchr(p + 1, ' ');
+ if (!p)
+ return -1;
+ }
+ if (sscanf(p, " %lu %lu", start_code, end_code) != 2)
+ return -1;
+ return 0;
+}
+
+/* Find the system loader through our own PT_INTERP. */
+static inline int find_loader(char *out, size_t sz)
+{
+ ElfW(Ehdr) eh;
+ ElfW(Phdr) ph;
+ int fd, i, ret = -1;
+
+ fd = open("/proc/self/exe", O_RDONLY);
+ if (fd < 0)
+ return -1;
+ if (pread(fd, &eh, sizeof(eh), 0) != sizeof(eh))
+ goto out;
+ for (i = 0; i < eh.e_phnum; i++) {
+ if (pread(fd, &ph, sizeof(ph),
+ eh.e_phoff + i * eh.e_phentsize) != sizeof(ph))
+ goto out;
+ if (ph.p_type != PT_INTERP)
+ continue;
+ if (!ph.p_filesz || ph.p_filesz > sz)
+ goto out;
+ if (pread(fd, out, ph.p_filesz, ph.p_offset) !=
+ (ssize_t)ph.p_filesz)
+ goto out;
+ out[ph.p_filesz - 1] = '\0';
+ ret = 0;
+ break;
+ }
+out:
+ close(fd);
+ return ret;
+}
+
+#endif /* __SELFTESTS_EXEC_BINFMT_MISC_COMMON_H */
diff --git a/tools/testing/selftests/exec/binfmt_misc_disabled.c b/tools/testing/selftests/exec/binfmt_misc_disabled.c
new file mode 100644
index 000000000000..47c9e8a4ee42
--- /dev/null
+++ b/tools/testing/selftests/exec/binfmt_misc_disabled.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test the 'D' (register disabled) flag of binfmt_misc. An entry
+ * registered with it exists but cannot be matched until userspace enables
+ * it, which splits a registration into create and activate.
+ *
+ * Needs root for the registration; no bpf toolchain involved.
+ */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "binfmt_misc_common.h"
+#include "kselftest_harness.h"
+
+#define MAGIC "#DISABLED-SELFTEST#"
+#define TARGET_PATH "/tmp/binfmt_disabled_target"
+#define INTERP_PATH "/tmp/binfmt_disabled_interp.sh"
+#define ENTRY "test_disabled"
+#define RULE(flags) ":" ENTRY ":M:0:" MAGIC "::" INTERP_PATH ":" flags
+
+/* The interpreter exits with a code the harness can recognise. */
+#define EXIT_INTERP 7
+
+/* The target only has to carry the magic; it is never actually loaded. */
+static int create_target(void)
+{
+ char buf[128] = MAGIC "\n";
+ int fd;
+
+ unlink(TARGET_PATH);
+ fd = open(TARGET_PATH, O_WRONLY | O_CREAT | O_EXCL, 0755);
+ if (fd < 0)
+ return -1;
+ if (write(fd, buf, sizeof(buf)) != (ssize_t)sizeof(buf)) {
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ return 0;
+}
+
+static int create_interp(void)
+{
+ char buf[64];
+ int fd;
+
+ unlink(INTERP_PATH);
+ fd = open(INTERP_PATH, O_WRONLY | O_CREAT | O_EXCL, 0755);
+ if (fd < 0)
+ return -1;
+ snprintf(buf, sizeof(buf), "#!/bin/sh\nexit %d\n", EXIT_INTERP);
+ if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf)) {
+ close(fd);
+ return -1;
+ }
+ return close(fd);
+}
+
+FIXTURE(disabled) {
+};
+
+FIXTURE_SETUP(disabled)
+{
+ if (getuid() != 0)
+ SKIP(return, "test must be run as root");
+ if (!binfmt_misc_available())
+ SKIP(return, "no binfmt_misc");
+
+ /* Skip the whole suite on a kernel that does not know 'D'. */
+ if (!binfmt_flag_supported('D')) {
+ ASSERT_EQ(errno, EINVAL);
+ SKIP(return, "kernel without the 'D' flag");
+ }
+
+ ASSERT_EQ(create_interp(), 0);
+ ASSERT_EQ(create_target(), 0);
+}
+
+FIXTURE_TEARDOWN(disabled)
+{
+ unregister(ENTRY);
+ unlink(TARGET_PATH);
+ unlink(INTERP_PATH);
+}
+
+/* The entry exists but does not dispatch until it is enabled. */
+TEST_F(disabled, inert_until_enabled)
+{
+ ASSERT_EQ(write_reg(RULE("D")), 0);
+ EXPECT_TRUE(entry_shows(ENTRY, "disabled"));
+
+ /* Nothing matches it, so no binary format claims the target. */
+ EXPECT_EQ(run_payload(TARGET_PATH), RUN_ENOEXEC);
+
+ ASSERT_EQ(entry_command(ENTRY, "1\n"), 0);
+ EXPECT_TRUE(entry_shows(ENTRY, "enabled"));
+ EXPECT_EQ(run_payload(TARGET_PATH), EXIT_INTERP);
+}
+
+/* Without 'D' an entry is matchable the moment it is registered. */
+TEST_F(disabled, enabled_without_the_flag)
+{
+ ASSERT_EQ(write_reg(RULE("")), 0);
+ EXPECT_TRUE(entry_shows(ENTRY, "enabled"));
+ EXPECT_EQ(run_payload(TARGET_PATH), EXIT_INTERP);
+}
+
+/* 'D' is spent on the registration: the entry does not report it back. */
+TEST_F(disabled, flag_not_reported)
+{
+ ASSERT_EQ(write_reg(RULE("D")), 0);
+ EXPECT_FALSE(entry_shows(ENTRY, "flags: D"));
+ EXPECT_TRUE(entry_shows(ENTRY, "flags: "));
+}
+
+/* A disabled entry can be disabled and enabled like any other. */
+TEST_F(disabled, toggles_like_any_entry)
+{
+ ASSERT_EQ(write_reg(RULE("D")), 0);
+
+ ASSERT_EQ(entry_command(ENTRY, "1\n"), 0);
+ ASSERT_EQ(run_payload(TARGET_PATH), EXIT_INTERP);
+ ASSERT_EQ(entry_command(ENTRY, "0\n"), 0);
+ EXPECT_EQ(run_payload(TARGET_PATH), RUN_ENOEXEC);
+ ASSERT_EQ(entry_command(ENTRY, "1\n"), 0);
+ EXPECT_EQ(run_payload(TARGET_PATH), EXIT_INTERP);
+}
+
+/* 'D' composes with the invocation flags a static entry can carry. */
+TEST_F(disabled, composes_with_invocation_flags)
+{
+ ASSERT_EQ(write_reg(RULE("PD")), 0);
+ EXPECT_TRUE(entry_shows(ENTRY, "disabled"));
+ EXPECT_TRUE(entry_shows(ENTRY, "flags: P"));
+}
+
+/* '-1' to the status file sweeps a staged entry with everything else. */
+TEST_F(disabled, removed_by_remove_all)
+{
+ int fd;
+
+ ASSERT_EQ(write_reg(RULE("D")), 0);
+ EXPECT_TRUE(entry_shows(ENTRY, "disabled"));
+
+ fd = open(BINFMT_DIR "/status", O_WRONLY | O_CLOEXEC);
+ ASSERT_GE(fd, 0);
+ ASSERT_EQ(write(fd, "-1", 2), 2);
+ close(fd);
+
+ EXPECT_NE(access(BINFMT_DIR "/" ENTRY, F_OK), 0);
+}
+
+/* A file handle held across a removal cannot resurrect the entry. */
+TEST_F(disabled, no_resurrection_after_remove)
+{
+ int fd;
+
+ ASSERT_EQ(write_reg(RULE("D")), 0);
+ fd = open(BINFMT_DIR "/" ENTRY, O_WRONLY | O_CLOEXEC);
+ ASSERT_GE(fd, 0);
+
+ ASSERT_EQ(write(fd, "-1", 2), 2);
+ EXPECT_NE(access(BINFMT_DIR "/" ENTRY, F_OK), 0);
+
+ /* Accepted like any toggle of a removed entry, but publishes nothing. */
+ EXPECT_EQ(write(fd, "1", 1), 1);
+ EXPECT_EQ(run_payload(TARGET_PATH), RUN_ENOEXEC);
+ close(fd);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/exec/binfmt_misc_interplimit.c b/tools/testing/selftests/exec/binfmt_misc_interplimit.c
new file mode 100644
index 000000000000..bf611c551784
--- /dev/null
+++ b/tools/testing/selftests/exec/binfmt_misc_interplimit.c
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A pre-opened interpreter - what 'F' gives a static entry and what a 'B'
+ * entry binds - keeps a file open for as long as the entry lives, so it pins
+ * the mount it came from. It costs no file descriptor, and binfmt_misc is
+ * FS_USERNS_MOUNT, so an unprivileged user namespace can create them without
+ * bound. Check that UCOUNT_BINFMT_MISC_INTERPRETERS bounds it, that an entry
+ * that pre-opens nothing is not charged, that removing an entry gives the
+ * charge back, and that nesting a user namespace does not evade it.
+ *
+ * Runs unprivileged in a user namespace.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "../filesystems/utils.h"
+#include "kselftest_harness.h"
+
+#define MNT "/tmp/binfmt_interplimit"
+#define NESTED_MNT "/tmp/binfmt_interplimit_nested"
+#define LIMIT_SYSCTL "/proc/sys/user/max_binfmt_misc_interpreters"
+
+#define MAGIC "\\xde\\xad"
+/* Not on the instance, and unlike /bin/true it always exists. */
+#define INTERP "/proc/self/exe"
+
+/* Small enough to fill by hand, big enough that a refund is visible. */
+#define LIMIT 4
+
+/* What UCOUNT_ENTRY() lets a namespace raise its own limit to. */
+#define LIMIT_MAX "2147483647"
+
+static int ensure_dir(const char *path)
+{
+ if (mkdir(path, 0755) && errno != EEXIST)
+ return -1;
+ return 0;
+}
+
+/* Write @val to @path, preserving write(2)'s errno for the caller. */
+static int write_keep_errno(const char *path, const char *val)
+{
+ int fd, saved;
+ ssize_t n;
+
+ fd = open(path, O_WRONLY | O_CLOEXEC);
+ if (fd < 0)
+ return -1;
+ n = write(fd, val, strlen(val));
+ saved = errno;
+ close(fd);
+ errno = saved;
+ return n < 0 ? -1 : 0;
+}
+
+static int set_limit(const char *val)
+{
+ return write_keep_errno(LIMIT_SYSCTL, val);
+}
+
+static int register_at(const char *mnt, const char *rule)
+{
+ char path[PATH_MAX];
+
+ snprintf(path, sizeof(path), "%s/register", mnt);
+ return write_keep_errno(path, rule);
+}
+
+/* An 'F' entry: one interpreter pre-opened at registration, one charge. */
+static int register_fixed(const char *mnt, const char *name)
+{
+ char rule[PATH_MAX];
+
+ snprintf(rule, sizeof(rule), ":%s:M::" MAGIC "::" INTERP ":F", name);
+ return register_at(mnt, rule);
+}
+
+/* The same entry without 'F': the interpreter is opened per exec instead. */
+static int register_plain(const char *mnt, const char *name)
+{
+ char rule[PATH_MAX];
+
+ snprintf(rule, sizeof(rule), ":%s:M::" MAGIC "::" INTERP ":", name);
+ return register_at(mnt, rule);
+}
+
+static int remove_entry(const char *mnt, const char *name)
+{
+ char path[PATH_MAX];
+
+ snprintf(path, sizeof(path), "%s/%s", mnt, name);
+ return write_keep_errno(path, "-1\n");
+}
+
+static bool entry_exists(const char *mnt, const char *name)
+{
+ char path[PATH_MAX];
+
+ snprintf(path, sizeof(path), "%s/%s", mnt, name);
+ return access(path, F_OK) == 0;
+}
+
+/* Register @n 'F' entries, each with a name of its own. */
+static int fill_budget(const char *mnt, unsigned int n)
+{
+ char name[32];
+ unsigned int i;
+
+ for (i = 0; i < n; i++) {
+ snprintf(name, sizeof(name), "fixed%u", i);
+ if (register_fixed(mnt, name))
+ return -1;
+ }
+ return 0;
+}
+
+FIXTURE(interp_limit) {
+};
+
+FIXTURE_SETUP(interp_limit)
+{
+ /* setup_userns() exits rather than returns if this is not there. */
+ if (access("/proc/self/ns/user", F_OK))
+ SKIP(return, "kernel without user namespaces");
+ ASSERT_EQ(setup_userns(), 0);
+
+ /* CAP_SYS_RESOURCE in this namespace is what makes it writable. */
+ if (set_limit(LIMIT_MAX)) {
+ if (errno == ENOENT)
+ SKIP(return, "kernel without " LIMIT_SYSCTL);
+ SKIP(return, "cannot set the limit: %s", strerror(errno));
+ }
+
+ ASSERT_EQ(ensure_dir(MNT), 0);
+ if (mount("binfmt_misc", MNT, "binfmt_misc", 0, NULL)) {
+ int saved = errno;
+
+ /* Teardown doesn't run when setup skips, so clean up here. */
+ rmdir(MNT);
+ SKIP(return, "no binfmt_misc: %s", strerror(saved));
+ }
+}
+
+FIXTURE_TEARDOWN(interp_limit)
+{
+ /* The namespaces go with the process; just don't litter /tmp. */
+ umount2(NESTED_MNT, MNT_DETACH);
+ umount2(MNT, MNT_DETACH);
+ rmdir(NESTED_MNT);
+ rmdir(MNT);
+}
+
+/* Every pre-opened interpreter is charged, and the budget is a hard stop. */
+TEST_F(interp_limit, fixed_interpreters_are_charged)
+{
+ char buf[32];
+
+ snprintf(buf, sizeof(buf), "%u", LIMIT);
+ ASSERT_EQ(set_limit(buf), 0);
+
+ ASSERT_EQ(fill_budget(MNT, LIMIT), 0);
+
+ EXPECT_NE(register_fixed(MNT, "over"), 0);
+ EXPECT_EQ(errno, ENOSPC);
+
+ /* A refused registration leaves nothing behind. */
+ EXPECT_FALSE(entry_exists(MNT, "over"));
+}
+
+/* An entry that pre-opens nothing pins nothing, so it is not charged. */
+TEST_F(interp_limit, plain_entries_are_not_charged)
+{
+ ASSERT_EQ(set_limit("0"), 0);
+
+ EXPECT_EQ(register_plain(MNT, "plain"), 0);
+ EXPECT_TRUE(entry_exists(MNT, "plain"));
+
+ /* ... while the same entry with 'F' has nothing to spend. */
+ EXPECT_NE(register_fixed(MNT, "fixed"), 0);
+ EXPECT_EQ(errno, ENOSPC);
+}
+
+/* Removing an entry closes its interpreters and gives the charge back. */
+TEST_F(interp_limit, removal_refunds_the_charge)
+{
+ char buf[32];
+
+ snprintf(buf, sizeof(buf), "%u", LIMIT);
+ ASSERT_EQ(set_limit(buf), 0);
+
+ ASSERT_EQ(fill_budget(MNT, LIMIT), 0);
+ ASSERT_NE(register_fixed(MNT, "over"), 0);
+
+ ASSERT_EQ(remove_entry(MNT, "fixed0"), 0);
+ EXPECT_EQ(register_fixed(MNT, "over"), 0);
+}
+
+/*
+ * The charge walks the ancestors, so a namespace cannot buy itself budget by
+ * nesting: it may raise only its own limit, and the parent it was created
+ * from is charged for every binding made below it.
+ */
+TEST_F(interp_limit, nesting_does_not_evade_it)
+{
+ char buf[32];
+
+ snprintf(buf, sizeof(buf), "%u", LIMIT);
+ ASSERT_EQ(set_limit(buf), 0);
+ ASSERT_EQ(fill_budget(MNT, LIMIT), 0);
+
+ ASSERT_EQ(setup_userns(), 0);
+ ASSERT_EQ(set_limit(LIMIT_MAX), 0);
+
+ ASSERT_EQ(ensure_dir(NESTED_MNT), 0);
+ ASSERT_EQ(mount("binfmt_misc", NESTED_MNT, "binfmt_misc", 0, NULL), 0);
+
+ /* A fresh instance with an unlimited budget of its own, and yet: */
+ EXPECT_NE(register_fixed(NESTED_MNT, "nested"), 0);
+ EXPECT_EQ(errno, ENOSPC);
+
+ /* The nested instance works for anything that pins no file. */
+ EXPECT_EQ(register_plain(NESTED_MNT, "nested_plain"), 0);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/exec/binfmt_misc_loader.c b/tools/testing/selftests/exec/binfmt_misc_loader.c
new file mode 100644
index 000000000000..1e14dcd274af
--- /dev/null
+++ b/tools/testing/selftests/exec/binfmt_misc_loader.c
@@ -0,0 +1,372 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test the 'L' (loader substitution) flag of binfmt_misc. A matched
+ * binary runs as the MAIN image - a fully native exec - with the
+ * registered interpreter substituted for its PT_INTERP. The payload
+ * (binfmt_loader_payload) asserts the native identity from inside.
+ *
+ * The substitute is a copy of the system loader found via our own
+ * PT_INTERP; magic matching pokes a marker into the ELF header's
+ * e_ident padding, which kernel and loader ignore.
+ *
+ * Needs root for the registration; no bpf toolchain involved.
+ */
+#define _GNU_SOURCE
+#include <elf.h>
+#include <link.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/ptrace.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+
+#include "binfmt_misc_common.h"
+#include "kselftest_harness.h"
+
+#define ENTRY "test_loader"
+#define INTERP_PATH "/tmp/binfmt_loader_interp"
+#define MOVED_PATH INTERP_PATH ".moved"
+#define TARGET_PATH "/tmp/binfmt_loader_target.ldrtest"
+#define STATIC_PATH "/tmp/binfmt_loader_static.ldrtest"
+#define FOREIGN_PATH "/tmp/binfmt_loader_foreign.ldrtest"
+#define SCRIPT_PATH "/tmp/binfmt_loader_script.ldrtest"
+#define M_RULE ":" ENTRY ":M:9:" LOADER_MARKER "::" INTERP_PATH ":L"
+#define E_RULE ":" ENTRY ":E::ldrtest::" INTERP_PATH ":L"
+#define FL_RULE ":" ENTRY ":E::ldrtest::" INTERP_PATH ":FL"
+
+/* Execute the binary from an inaccessible O_CLOEXEC memfd. */
+static int run_memfd(const char *path)
+{
+ int status;
+ pid_t pid;
+
+ pid = fork();
+ if (pid == 0) {
+ char *argv[] = { PAYLOAD_ARGV0, PAYLOAD_ARG1, PAYLOAD_ARG2, NULL };
+ char buf[4096];
+ int in, mfd;
+ ssize_t n;
+
+ mfd = memfd_create("loader-test", MFD_CLOEXEC);
+ in = open(path, O_RDONLY);
+ if (mfd < 0 || in < 0)
+ _exit(125);
+ while ((n = read(in, buf, sizeof(buf))) > 0)
+ if (write(mfd, buf, n) != n)
+ _exit(125);
+ close(in);
+ setenv("BINFMT_TEST_MEMFD", "1", 1);
+ unsetenv("BINFMT_TEST_BINARY");
+ syscall(SYS_execveat, mfd, "", argv, environ, AT_EMPTY_PATH);
+ _exit(126);
+ }
+ if (pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status))
+ return -1;
+ return WEXITSTATUS(status);
+}
+
+/*
+ * The differentiator against the transparent mode: at PTRACE_EVENT_EXEC
+ * the identity is already complete - exe, auxv and the stat code markers
+ * are mutually consistent with no window a debugger could observe.
+ */
+static int ptrace_probe(const char *target)
+{
+ unsigned long auxv[2 * 64], base = 0, entry = 0, at_flags = 0;
+ unsigned long start_code = 0, end_code = 0;
+ int status, fd, execfd_seen = 0, failed = 0;
+ char path[64], buf[PATH_MAX];
+ ssize_t n;
+ pid_t pid;
+ int i;
+
+ pid = fork();
+ if (pid == 0) {
+ ptrace(PTRACE_TRACEME, 0, NULL, NULL);
+ raise(SIGSTOP);
+ execl(target, PAYLOAD_ARGV0, PAYLOAD_ARG1, PAYLOAD_ARG2, (char *)NULL);
+ _exit(126);
+ }
+ if (pid < 0)
+ return -1;
+ if (waitpid(pid, &status, 0) != pid || !WIFSTOPPED(status))
+ goto fail_kill;
+ if (ptrace(PTRACE_SETOPTIONS, pid, NULL, (void *)PTRACE_O_TRACEEXEC))
+ goto fail_kill;
+ if (ptrace(PTRACE_CONT, pid, NULL, NULL))
+ goto fail_kill;
+ if (waitpid(pid, &status, 0) != pid || !WIFSTOPPED(status) ||
+ status >> 8 != (SIGTRAP | (PTRACE_EVENT_EXEC << 8))) {
+ fprintf(stderr, "no exec stop (status %#x)\n", status);
+ goto fail_kill;
+ }
+
+ snprintf(path, sizeof(path), "/proc/%d/exe", pid);
+ n = readlink(path, buf, sizeof(buf) - 1);
+ if (n <= 0) {
+ failed = 1;
+ } else {
+ buf[n] = '\0';
+ if (strcmp(buf, target)) {
+ fprintf(stderr, "exe at exec stop: %s\n", buf);
+ failed = 1;
+ }
+ }
+
+ snprintf(path, sizeof(path), "/proc/%d/auxv", pid);
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ n = -1;
+ } else {
+ n = read(fd, auxv, sizeof(auxv));
+ close(fd);
+ }
+ if (n <= 0) {
+ failed = 1;
+ n = 0;
+ }
+ for (i = 0; i + 1 < (int)(n / sizeof(unsigned long)); i += 2) {
+ switch (auxv[i]) {
+ case AT_BASE:
+ base = auxv[i + 1];
+ break;
+ case AT_ENTRY:
+ entry = auxv[i + 1];
+ break;
+ case AT_FLAGS:
+ at_flags = auxv[i + 1];
+ break;
+ case AT_EXECFD:
+ execfd_seen = 1;
+ break;
+ }
+ }
+
+ if (stat_codes(pid, &start_code, &end_code))
+ failed = 1;
+
+ if (!base || execfd_seen || at_flags) {
+ fprintf(stderr, "auxv at exec stop not native\n");
+ failed = 1;
+ }
+ if (!start_code || entry < start_code || entry >= end_code) {
+ fprintf(stderr, "auxv/stat inconsistent at exec stop\n");
+ failed = 1;
+ }
+
+ if (ptrace(PTRACE_CONT, pid, NULL, NULL))
+ goto fail_kill;
+ if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status) ||
+ WEXITSTATUS(status))
+ failed = 1;
+ return failed ? -1 : 0;
+
+fail_kill:
+ kill(pid, SIGKILL);
+ waitpid(pid, &status, 0);
+ return -1;
+}
+
+FIXTURE(loader) {
+ bool have_static;
+};
+
+FIXTURE_SETUP(loader)
+{
+ unsigned short foreign_machine = 0xdead;
+ char src[PATH_MAX], loader[PATH_MAX];
+
+ if (getuid() != 0)
+ SKIP(return, "test must be run as root");
+ if (!binfmt_misc_available())
+ SKIP(return, "no binfmt_misc");
+ if (find_loader(loader, sizeof(loader)))
+ SKIP(return, "cannot determine own PT_INTERP");
+
+ ASSERT_EQ(copy_file(loader, INTERP_PATH), 0);
+
+ ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_loader_payload"), 0);
+ ASSERT_EQ(copy_file(src, TARGET_PATH), 0);
+ ASSERT_EQ(patch_file(TARGET_PATH, EI_PAD, LOADER_MARKER,
+ strlen(LOADER_MARKER)), 0);
+
+ /* The same payload with a machine type this kernel cannot load. */
+ ASSERT_EQ(copy_file(src, FOREIGN_PATH), 0);
+ ASSERT_EQ(patch_file(FOREIGN_PATH, EI_PAD, LOADER_MARKER,
+ strlen(LOADER_MARKER)), 0);
+ ASSERT_EQ(patch_file(FOREIGN_PATH, offsetof(ElfW(Ehdr), e_machine),
+ &foreign_machine, sizeof(foreign_machine)), 0);
+
+ self->have_static =
+ artifact_path(src, sizeof(src), "binfmt_loader_payload_static") == 0 &&
+ copy_file(src, STATIC_PATH) == 0;
+
+ setenv("BINFMT_TEST_BINARY", TARGET_PATH, 1);
+ setenv("BINFMT_TEST_INTERP", INTERP_PATH, 1);
+
+ /* Everything below needs the flag; find out once. */
+ if (write_reg(E_RULE)) {
+ ASSERT_EQ(errno, EINVAL);
+ SKIP(return, "kernel without the 'L' flag");
+ }
+ unregister(ENTRY);
+}
+
+FIXTURE_TEARDOWN(loader)
+{
+ unregister(ENTRY);
+ if (access(MOVED_PATH, F_OK) == 0)
+ rename(MOVED_PATH, INTERP_PATH);
+ unlink(TARGET_PATH);
+ unlink(STATIC_PATH);
+ unlink(FOREIGN_PATH);
+ unlink(SCRIPT_PATH);
+ unlink(INTERP_PATH);
+}
+
+/* Grammar sanity check: the same entry without 'L' has to register. */
+TEST_F(loader, plain_entry_registers)
+{
+ ASSERT_EQ(write_reg(":" ENTRY ":E::ldrtest::" INTERP_PATH ":"), 0);
+}
+
+/* 'L' is a native exec: every classic-dispatch flag is rejected. */
+TEST_F(loader, rejects_classic_flags)
+{
+ static const char * const combos[] = { "LT", "LP", "LC", "LO" };
+ char rule[PATH_MAX];
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(combos); i++) {
+ int rc;
+
+ snprintf(rule, sizeof(rule),
+ ":" ENTRY ":E::ldrtest::" INTERP_PATH ":%s", combos[i]);
+ rc = write_reg(rule);
+ EXPECT_EQ(rc, -1)
+ TH_LOG("'%s' was not rejected", combos[i]);
+ if (rc == 0) {
+ unregister(ENTRY);
+ continue;
+ }
+ EXPECT_EQ(errno, EINVAL);
+ }
+}
+
+/*
+ * Without 'F' the interpreter is opened when the binary is executed, so a
+ * relative path would be resolved against the caller's working directory.
+ */
+TEST_F(loader, rejects_relative_interpreter)
+{
+ static const char * const flags[] = { "L", "C" };
+ char rule[PATH_MAX];
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(flags); i++) {
+ int rc;
+
+ snprintf(rule, sizeof(rule),
+ ":" ENTRY ":E::ldrtest::binfmt_loader_interp:%s",
+ flags[i]);
+ rc = write_reg(rule);
+ EXPECT_EQ(rc, -1)
+ TH_LOG("'%s' accepted a relative interpreter", flags[i]);
+ if (rc == 0) {
+ unregister(ENTRY);
+ continue;
+ }
+ EXPECT_EQ(errno, EINVAL);
+ }
+}
+
+TEST_F(loader, extension_matched)
+{
+ ASSERT_EQ(write_reg(E_RULE), 0);
+ EXPECT_EQ(run_payload(TARGET_PATH), 0);
+}
+
+TEST_F(loader, magic_matched)
+{
+ ASSERT_EQ(write_reg(M_RULE), 0);
+ EXPECT_EQ(run_payload(TARGET_PATH), 0);
+}
+
+/*
+ * The differentiator against the transparent mode: at PTRACE_EVENT_EXEC the
+ * identity is already complete, with no window a debugger could observe.
+ */
+TEST_F(loader, exec_stop_consistency)
+{
+ ASSERT_EQ(write_reg(E_RULE), 0);
+ EXPECT_EQ(ptrace_probe(TARGET_PATH), 0);
+}
+
+/* A binary without PT_INTERP drops the override and runs natively. */
+TEST_F(loader, static_binary_runs_natively)
+{
+ if (!self->have_static)
+ SKIP(return, "no static payload built");
+
+ ASSERT_EQ(write_reg(E_RULE), 0);
+ setenv("BINFMT_TEST_BINARY", STATIC_PATH, 1);
+ setenv("BINFMT_TEST_STATIC", "1", 1);
+ EXPECT_EQ(run_payload(STATIC_PATH), 0);
+ unsetenv("BINFMT_TEST_STATIC");
+ setenv("BINFMT_TEST_BINARY", TARGET_PATH, 1);
+}
+
+/*
+ * A '#!' file that matched an 'L' entry is claimed by binfmt_script, which
+ * sits ahead of binfmt_elf. The substitute the entry staged has to be
+ * released when the interpreter replaces the file, not leaked.
+ */
+TEST_F(loader, script_claims_the_file)
+{
+ static const char script[] = "#!/bin/sh\nexit 0\n";
+ int fd;
+
+ unlink(SCRIPT_PATH);
+ fd = open(SCRIPT_PATH, O_WRONLY | O_CREAT | O_EXCL, 0755);
+ ASSERT_GE(fd, 0);
+ ASSERT_EQ(write(fd, script, sizeof(script) - 1),
+ (ssize_t)sizeof(script) - 1);
+ ASSERT_EQ(close(fd), 0);
+
+ ASSERT_EQ(write_reg(E_RULE), 0);
+ EXPECT_EQ(run_payload(SCRIPT_PATH), 0);
+
+ /* A leaked substitute keeps its write denial on the loader. */
+ fd = open(INTERP_PATH, O_WRONLY);
+ EXPECT_GE(fd, 0)
+ TH_LOG("loader still write denied (errno %d)", errno);
+ if (fd >= 0)
+ close(fd);
+}
+
+/* Nothing needs the binary's path, so an inaccessible fd works. */
+TEST_F(loader, inaccessible_memfd)
+{
+ ASSERT_EQ(write_reg(M_RULE), 0);
+ EXPECT_EQ(run_memfd(TARGET_PATH), 0);
+}
+
+/* The whole exec of a wrong-arch binary fails as if unhandled. */
+TEST_F(loader, foreign_arch_enoexec)
+{
+ ASSERT_EQ(write_reg(M_RULE), 0);
+ EXPECT_EQ(run_payload(FOREIGN_PATH), RUN_ENOEXEC);
+}
+
+/* 'F' pre-opens the substitute, so it survives losing its path. */
+TEST_F(loader, fixed_interpreter_survives_rename)
+{
+ ASSERT_EQ(write_reg(FL_RULE), 0);
+ ASSERT_EQ(rename(INTERP_PATH, MOVED_PATH), 0);
+ EXPECT_EQ(run_payload(TARGET_PATH), 0);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/exec/binfmt_misc_selfpin.c b/tools/testing/selftests/exec/binfmt_misc_selfpin.c
new file mode 100644
index 000000000000..5286b0604eed
--- /dev/null
+++ b/tools/testing/selftests/exec/binfmt_misc_selfpin.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * An 'F' entry keeps its interpreter open for as long as the entry exists,
+ * and the entry only goes away when the binfmt_misc superblock is destroyed.
+ * An interpreter that lives on a mount which in turn keeps that superblock
+ * alive therefore pins the instance that owns it, and nothing can break the
+ * cycle. Check the two ways userspace could arrange for that: an interpreter
+ * on the binfmt_misc instance itself, and one on a filesystem stacked on it.
+ *
+ * Runs unprivileged in a user namespace; binfmt_misc is FS_USERNS_MOUNT.
+ */
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <limits.h>
+#include <sched.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+
+#include "../filesystems/utils.h"
+#include "kselftest_harness.h"
+
+#define MNT "/tmp/binfmt_selfpin"
+#define BACKING "/tmp/binfmt_selfpin_back"
+#define LOWER BACKING "/lower"
+#define MERGED "/tmp/binfmt_selfpin_merged"
+
+#define MAGIC "\\xde\\xad"
+#define RULE(interp) ":selfpin:M::" MAGIC "::" interp ":F"
+/* Not on the instance, and unlike /bin/true it always exists. */
+#define INTERP "/proc/self/exe"
+
+#define OPTS_MAX (3 * PATH_MAX + 64)
+
+static int ensure_dir(const char *path)
+{
+ if (mkdir(path, 0755) && errno != EEXIST)
+ return -1;
+ return 0;
+}
+
+/* Write @rule to this instance's register file, preserving write(2)'s errno. */
+static int register_at(struct __test_metadata *_metadata, const char *rule)
+{
+ int fd, saved;
+ ssize_t n;
+
+ fd = open(MNT "/register", O_WRONLY);
+ ASSERT_GE(fd, 0);
+ n = write(fd, rule, strlen(rule));
+ saved = errno;
+ close(fd);
+ errno = saved;
+ return n < 0 ? -1 : 0;
+}
+
+/*
+ * Mount an overlay over @lower using a private upper/work pair, so the two
+ * mounts this test performs cannot interfere with each other and neither
+ * overlaps the lower layer.
+ */
+static int mount_overlay(const char *lower, int nr)
+{
+ char opts[OPTS_MAX], upper[PATH_MAX], work[PATH_MAX];
+
+ snprintf(upper, sizeof(upper), "%s/upper%d", BACKING, nr);
+ snprintf(work, sizeof(work), "%s/work%d", BACKING, nr);
+ if (mkdir(upper, 0755) || mkdir(work, 0755))
+ return -1;
+
+ snprintf(opts, sizeof(opts), "lowerdir=%s,upperdir=%s,workdir=%s",
+ lower, upper, work);
+ return mount("ovl", MERGED, "overlay", 0, opts);
+}
+
+FIXTURE(selfpin) {
+};
+
+FIXTURE_SETUP(selfpin)
+{
+ /* setup_userns() exits rather than returns if this is not there. */
+ if (access("/proc/self/ns/user", F_OK))
+ SKIP(return, "kernel without user namespaces");
+ ASSERT_EQ(setup_userns(), 0);
+
+ ASSERT_EQ(ensure_dir(MNT), 0);
+ if (mount("binfmt_misc", MNT, "binfmt_misc", 0, NULL)) {
+ int saved = errno;
+
+ /* Teardown doesn't run when setup skips, so clean up here. */
+ rmdir(MNT);
+ SKIP(return, "no binfmt_misc: %s", strerror(saved));
+ }
+}
+
+FIXTURE_TEARDOWN(selfpin)
+{
+ /* The namespaces go with the process; just don't litter /tmp. */
+ umount2(MERGED, MNT_DETACH);
+ umount2(BACKING, MNT_DETACH);
+ umount2(MNT, MNT_DETACH);
+ rmdir(MERGED);
+ rmdir(BACKING);
+ rmdir(MNT);
+}
+
+/*
+ * The instance's own files are regular files the mounter owns, so they can be
+ * made executable. Opening one for exec still has to fail, otherwise the entry
+ * pins the very superblock it lives in.
+ */
+TEST_F(selfpin, interpreter_on_the_instance)
+{
+ ASSERT_EQ(chmod(MNT "/status", 0755), 0);
+
+ ASSERT_NE(register_at(_metadata, RULE(MNT "/status")), 0);
+ EXPECT_EQ(errno, EACCES);
+}
+
+/* Same for an entry file rather than one of the control files. */
+TEST_F(selfpin, interpreter_on_an_entry)
+{
+ ASSERT_EQ(register_at(_metadata, ":victim:M::" MAGIC "::" INTERP ":"), 0);
+ ASSERT_EQ(chmod(MNT "/victim", 0755), 0);
+
+ ASSERT_NE(register_at(_metadata, RULE(MNT "/victim")), 0);
+ EXPECT_EQ(errno, EACCES);
+}
+
+/*
+ * A stacking filesystem holds a private clone of each layer for its whole
+ * lifetime, so an instance used as a layer can be pinned by an interpreter
+ * that does not live on it at all. Refuse to be a layer.
+ */
+TEST_F(selfpin, refuses_to_be_stacked_on)
+{
+ ASSERT_EQ(ensure_dir(BACKING), 0);
+ ASSERT_EQ(mount("tmpfs", BACKING, "tmpfs", 0, NULL), 0);
+ ASSERT_EQ(mkdir(LOWER, 0755), 0);
+ ASSERT_EQ(ensure_dir(MERGED), 0);
+
+ /* Nothing to prove unless overlayfs works here at all. */
+ if (mount_overlay(LOWER, 1)) {
+ if (errno == ENODEV || errno == EPERM)
+ SKIP(return, "no unprivileged overlayfs");
+ SKIP(return, "overlayfs unusable here: %s", strerror(errno));
+ }
+ ASSERT_EQ(umount(MERGED), 0);
+
+ EXPECT_NE(mount_overlay(MNT, 2), 0);
+}
+
+/* An ordinary interpreter still registers with 'F'. */
+TEST_F(selfpin, ordinary_interpreter_still_works)
+{
+ EXPECT_EQ(register_at(_metadata, RULE(INTERP)), 0);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/exec/binfmt_misc_transparent.c b/tools/testing/selftests/exec/binfmt_misc_transparent.c
new file mode 100644
index 000000000000..2ebf73de8018
--- /dev/null
+++ b/tools/testing/selftests/exec/binfmt_misc_transparent.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test the static transparent flag 'T' of binfmt_misc. A magic-matched
+ * binary is dispatched to an interpreter with the argument vector left
+ * untouched, the binary passed through AT_EXECFD and mm->exe_file labeled
+ * with the binary. The asserting interpreter (binfmt_transparent_interp)
+ * verifies the constructed identity from inside the process and exits 0.
+ *
+ * Needs root for the registration; no bpf toolchain involved.
+ */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "binfmt_misc_common.h"
+#include "kselftest_harness.h"
+
+#define MAGIC "#TRANSPARENT-SELFTEST#"
+#define TARGET_PATH "/tmp/binfmt_transparent_target"
+#define INTERP_PATH "/tmp/binfmt_transparent_interp"
+#define ENTRY "test_transparent"
+#define RULE(flags) ":" ENTRY ":M:0:" MAGIC "::" INTERP_PATH ":" flags
+
+/* The target only has to carry the magic; it is never actually loaded. */
+static int create_target(void)
+{
+ char buf[128] = MAGIC "\n";
+ int fd;
+
+ unlink(TARGET_PATH);
+ fd = open(TARGET_PATH, O_WRONLY | O_CREAT | O_EXCL, 0755);
+ if (fd < 0)
+ return -1;
+ if (write(fd, buf, sizeof(buf)) != (ssize_t)sizeof(buf)) {
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ return 0;
+}
+
+FIXTURE(transparent) {
+};
+
+FIXTURE_SETUP(transparent)
+{
+ char src[PATH_MAX];
+
+ if (getuid() != 0)
+ SKIP(return, "test must be run as root");
+ if (!binfmt_misc_available())
+ SKIP(return, "no binfmt_misc");
+
+ ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_transparent_interp"), 0);
+ ASSERT_EQ(copy_file(src, INTERP_PATH), 0);
+ ASSERT_EQ(create_target(), 0);
+
+ /* Skip the whole suite on a kernel that does not know 'T'. */
+ if (!binfmt_flag_supported('T')) {
+ ASSERT_EQ(errno, EINVAL);
+ SKIP(return, "kernel without the 'T' flag");
+ }
+}
+
+FIXTURE_TEARDOWN(transparent)
+{
+ unregister(ENTRY);
+ unlink(TARGET_PATH);
+ unlink(INTERP_PATH);
+}
+
+/* Grammar sanity check: the same entry without 'T' has to register. */
+TEST_F(transparent, plain_entry_registers)
+{
+ ASSERT_EQ(write_reg(RULE("")), 0);
+}
+
+/* 'T' preserves the whole argv, so combining it with 'P' is rejected. */
+TEST_F(transparent, rejects_preserve_argv0)
+{
+ ASSERT_NE(write_reg(RULE("TP")), 0);
+ EXPECT_EQ(errno, EINVAL);
+}
+
+/* The interpreter asserts the identity the kernel built for it. */
+TEST_F(transparent, dispatch)
+{
+ ASSERT_EQ(write_reg(RULE("T")), 0);
+
+ setenv("BINFMT_TEST_BINARY", TARGET_PATH, 1);
+ setenv("BINFMT_TEST_ARGV0", PAYLOAD_ARGV0, 1);
+ EXPECT_EQ(run_payload(TARGET_PATH), 0);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/exec/binfmt_transparent_interp.c b/tools/testing/selftests/exec/binfmt_transparent_interp.c
new file mode 100644
index 000000000000..d4c4a538c9aa
--- /dev/null
+++ b/tools/testing/selftests/exec/binfmt_transparent_interp.c
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Asserting interpreter for the transparent binfmt_misc mode. It runs in
+ * place of the dispatched binary and verifies the identity the kernel
+ * constructed: the aux vector contract, the exe link, argv, cmdline, comm
+ * and the write denial on the binary. BINFMT_TEST_BINARY names the binary;
+ * the harness execs it with the arguments "argone argtwo". Prints
+ * TRANSPARENT_OK and exits 0 when every check holds.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/auxv.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "binfmt_misc_common.h"
+#include "kselftest.h"
+
+#ifndef AT_FLAGS_TRANSPARENT_INTERP
+#define AT_FLAGS_TRANSPARENT_INTERP (1 << 1)
+#endif
+
+static int fail;
+
+static void ok(int cond, const char *what)
+{
+ if (!cond) {
+ fprintf(stderr, "TRANSPARENT_FAIL: %s (errno %d)\n", what, errno);
+ fail = 1;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ const char *binary = getenv("BINFMT_TEST_BINARY");
+ const char *argv0 = getenv("BINFMT_TEST_ARGV0");
+ char expect[PATH_MAX + 32], buf[PATH_MAX];
+ unsigned long execfd;
+ struct stat stb, stfd;
+ const char *want[3];
+ const char *base;
+ size_t expect_len, i;
+ int fd, have_stb, have_stfd;
+ ssize_t n;
+
+ if (!binary) {
+ fprintf(stderr, "TRANSPARENT_FAIL: BINFMT_TEST_BINARY unset\n");
+ return 1;
+ }
+ /* Distinct from the binary path, so a classic argv splice is caught. */
+ want[0] = argv0 ? argv0 : binary;
+ want[1] = PAYLOAD_ARG1;
+ want[2] = PAYLOAD_ARG2;
+
+ /* The aux vector announces the transparent contract. */
+ ok(getauxval(AT_FLAGS) & AT_FLAGS_TRANSPARENT_INTERP,
+ "AT_FLAGS lacks AT_FLAGS_TRANSPARENT_INTERP");
+
+ /* AT_EXECFD refers to the very file that was executed. */
+ execfd = getauxval(AT_EXECFD);
+ ok(execfd > 2, "no AT_EXECFD");
+ have_stb = !stat(binary, &stb);
+ ok(have_stb, "cannot stat the binary");
+ have_stfd = !fstat((int)execfd, &stfd);
+ ok(have_stfd, "cannot fstat AT_EXECFD");
+ ok(have_stb && have_stfd && stb.st_dev == stfd.st_dev &&
+ stb.st_ino == stfd.st_ino, "AT_EXECFD is not the binary");
+
+ /* The exe link names the binary, not this interpreter. */
+ ok(exe_is(binary), "/proc/self/exe is not the binary");
+
+ /* argv arrived unspliced. */
+ ok(argc == (int)ARRAY_SIZE(want), "argv was rewritten");
+ for (i = 0; i < ARRAY_SIZE(want) && i < (size_t)argc; i++)
+ ok(!strcmp(argv[i], want[i]), "argv was rewritten");
+
+ /* And so did the kernel's copy of it: the same strings, NUL separated. */
+ for (i = 0, expect_len = 0; i < ARRAY_SIZE(want); i++) {
+ size_t len = strlen(want[i]) + 1;
+
+ if (expect_len + len > sizeof(expect)) {
+ ok(0, "argv does not fit the expectation buffer");
+ break;
+ }
+ memcpy(expect + expect_len, want[i], len);
+ expect_len += len;
+ }
+ fd = open("/proc/self/cmdline", O_RDONLY);
+ n = fd >= 0 ? read(fd, buf, sizeof(buf)) : -1;
+ if (fd >= 0)
+ close(fd);
+ ok(n == (ssize_t)expect_len && !memcmp(buf, expect, expect_len),
+ "/proc/self/cmdline was rewritten");
+
+ /* comm is the binary's basename. */
+ base = strrchr(binary, '/');
+ base = base ? base + 1 : binary;
+ ok(comm_is(base), "comm is not the binary's basename");
+
+ /* The binary is write-denied while it runs, like a direct exec. */
+ ok(write_denied(binary), "binary is writable while running");
+ ok(write_denied("/proc/self/exe"), "exe link is writable while running");
+
+ if (!fail)
+ printf("TRANSPARENT_OK\n");
+ return fail;
+}
diff --git a/tools/testing/selftests/exec/bpf_interp.bpf.c b/tools/testing/selftests/exec/bpf_interp.bpf.c
new file mode 100644
index 000000000000..8df2d2d01e25
--- /dev/null
+++ b/tools/testing/selftests/exec/bpf_interp.bpf.c
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * binfmt_misc_ops handler for the selftest's fixed-interpreter case: match a
+ * 64-bit aarch64 ELF header from the prefetched buffer and route it to a fixed
+ * interpreter chosen by the program. This is the portable, self-contained
+ * equivalent of routing a foreign binary to an emulator: it matches
+ * programmatically and computes the interpreter, but points at a test binary
+ * the harness installs rather than a system emulator.
+ */
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+#define EI_CLASS 4
+#define ELFCLASS64 2
+#define EM_AARCH64 183
+
+extern int bpf_binprm_set_interp(struct linux_binprm *bprm, const char *path,
+ size_t path__sz) __ksym;
+
+/*
+ * A magic-style decision needs nothing beyond the prefetched bprm->buf,
+ * even though the match program could read the file.
+ */
+SEC("struct_ops.s/match")
+bool BPF_PROG(bpf_interp_match, struct linux_binprm *bprm)
+{
+ __u16 machine;
+
+ if (bprm->buf[0] != 0x7f || bprm->buf[1] != 'E' ||
+ bprm->buf[2] != 'L' || bprm->buf[3] != 'F' ||
+ bprm->buf[EI_CLASS] != ELFCLASS64)
+ return false;
+
+ /* e_machine is a 16-bit little-endian field at offset 18. */
+ machine = (__u8)bprm->buf[18] | ((__u16)(__u8)bprm->buf[19] << 8);
+ return machine == EM_AARCH64;
+}
+
+SEC("struct_ops.s/load")
+int BPF_PROG(bpf_interp_load, struct linux_binprm *bprm)
+{
+ /*
+ * Keep the path on the (writable) stack: bpf_binprm_set_interp() takes
+ * a sized memory arg and the verifier rejects a read-only .rodata
+ * buffer for it. The harness installs the interpreter at this path.
+ */
+ char interp[] = "/tmp/binfmt_bpf_interp";
+
+ /* @path__sz includes the terminating NUL; 0 commits the selection. */
+ return bpf_binprm_set_interp(bprm, interp, sizeof(interp));
+}
+
+SEC(".struct_ops.link")
+struct binfmt_misc_ops bpf_interp = {
+ .match = (void *)bpf_interp_match,
+ .load = (void *)bpf_interp_load,
+ .name = "bpf_interp",
+};
diff --git a/tools/testing/selftests/exec/config b/tools/testing/selftests/exec/config
index c308079867b3..ea359a929ae8 100644
--- a/tools/testing/selftests/exec/config
+++ b/tools/testing/selftests/exec/config
@@ -1,2 +1,12 @@
CONFIG_BLK_DEV=y
CONFIG_BLK_DEV_LOOP=y
+CONFIG_BINFMT_MISC=y
+CONFIG_BINFMT_MISC_BPF=y
+CONFIG_BPF_JIT=y
+CONFIG_BPF_SYSCALL=y
+CONFIG_DEBUG_INFO=y
+CONFIG_DEBUG_INFO_BTF=y
+CONFIG_DEBUG_INFO_DWARF4=y
+CONFIG_OVERLAY_FS=y
+CONFIG_TMPFS=y
+CONFIG_USER_NS=y
diff --git a/tools/testing/selftests/exec/interp_bind.bpf.c b/tools/testing/selftests/exec/interp_bind.bpf.c
new file mode 100644
index 000000000000..1ce45cca215f
--- /dev/null
+++ b/tools/testing/selftests/exec/interp_bind.bpf.c
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * binfmt_misc_ops handler for the selftest's bound-interpreter case: one
+ * handler, one entry, an interpreter per guest architecture - each bound to
+ * a file when the entry was registered rather than to a path resolved at
+ * exec time. The load program names the one it wants; a name the entry did
+ * not bind fails the exec, which the harness checks too.
+ */
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+#define EI_CLASS 4
+#define ELFCLASS64 2
+#define E_MACHINE_OFF 18
+#define EM_ARM 40
+#define EM_AARCH64 183
+#define EM_RISCV 243
+
+extern int bpf_binprm_select_interp(struct linux_binprm *bprm,
+ const char *name, size_t name__sz) __ksym;
+
+/* The guest architecture of a 64-bit ELF, or zero if it is not one. */
+static __u16 elf_machine(struct linux_binprm *bprm)
+{
+ if (bprm->buf[0] != 0x7f || bprm->buf[1] != 'E' ||
+ bprm->buf[2] != 'L' || bprm->buf[3] != 'F' ||
+ bprm->buf[EI_CLASS] != ELFCLASS64)
+ return 0;
+
+ /* Little-endian 16-bit field, read byte-wise for the verifier. */
+ return (__u8)bprm->buf[E_MACHINE_OFF] |
+ ((__u16)(__u8)bprm->buf[E_MACHINE_OFF + 1] << 8);
+}
+
+SEC("struct_ops.s/match")
+bool BPF_PROG(interp_bind_match, struct linux_binprm *bprm)
+{
+ __u16 machine = elf_machine(bprm);
+
+ return machine == EM_AARCH64 || machine == EM_RISCV ||
+ machine == EM_ARM;
+}
+
+SEC("struct_ops.s/load")
+int BPF_PROG(interp_bind_load, struct linux_binprm *bprm)
+{
+ /*
+ * Names, not paths: each one selects a file the entry pre-opened, so
+ * nothing is resolved here or later, in any namespace. The buffers
+ * are on the stack because the verifier rejects .rodata for a sized
+ * memory argument.
+ */
+ char first[] = "first";
+ char second[] = "second";
+ char unbound[] = "unbound";
+
+ switch (elf_machine(bprm)) {
+ case EM_AARCH64:
+ return bpf_binprm_select_interp(bprm, first, sizeof(first));
+ case EM_RISCV:
+ return bpf_binprm_select_interp(bprm, second, sizeof(second));
+ }
+
+ /* The entry bound nothing under this name: -ENOENT fails the exec. */
+ return bpf_binprm_select_interp(bprm, unbound, sizeof(unbound));
+}
+
+SEC(".struct_ops.link")
+struct binfmt_misc_ops interp_bind = {
+ .match = (void *)interp_bind_match,
+ .load = (void *)interp_bind_load,
+ .name = "interp_bind",
+};
diff --git a/tools/testing/selftests/exec/loader.bpf.c b/tools/testing/selftests/exec/loader.bpf.c
new file mode 100644
index 000000000000..108e51dd4961
--- /dev/null
+++ b/tools/testing/selftests/exec/loader.bpf.c
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * binfmt_misc_ops handler for the loader-substitution case: match the
+ * marker the harness poked into the payload's e_ident padding and ask for
+ * the selected interpreter to be substituted for the binary's PT_INTERP,
+ * so the binary itself runs as a fully native exec.
+ */
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+#define EI_CLASS 4
+#define EI_PAD 9
+#define ELFCLASS64 2
+
+extern int bpf_binprm_set_interp(struct linux_binprm *bprm, const char *path,
+ size_t path__sz) __ksym;
+extern int bpf_binprm_set_flags(struct linux_binprm *bprm,
+ enum bpf_binprm_flags flags) __ksym;
+
+SEC("struct_ops.s/match")
+bool BPF_PROG(loader_match, struct linux_binprm *bprm)
+{
+ if (bprm->buf[0] != 0x7f || bprm->buf[1] != 'E' ||
+ bprm->buf[2] != 'L' || bprm->buf[3] != 'F' ||
+ bprm->buf[EI_CLASS] != ELFCLASS64)
+ return false;
+
+ /* The harness marks the payload with "LDRTST" at EI_PAD. */
+ return bprm->buf[EI_PAD + 0] == 'L' && bprm->buf[EI_PAD + 1] == 'D' &&
+ bprm->buf[EI_PAD + 2] == 'R' && bprm->buf[EI_PAD + 3] == 'T' &&
+ bprm->buf[EI_PAD + 4] == 'S' && bprm->buf[EI_PAD + 5] == 'T';
+}
+
+SEC("struct_ops.s/load")
+int BPF_PROG(loader_load, struct linux_binprm *bprm)
+{
+ char interp[] = "/tmp/binfmt_loader_interp";
+ int err;
+
+ err = bpf_binprm_set_flags(bprm, BPF_BINPRM_LOADER);
+ if (err)
+ return err;
+
+ /* @path__sz includes the terminating NUL; 0 commits the selection. */
+ return bpf_binprm_set_interp(bprm, interp, sizeof(interp));
+}
+
+SEC(".struct_ops.link")
+struct binfmt_misc_ops loader = {
+ .match = (void *)loader_match,
+ .load = (void *)loader_load,
+ .name = "loader",
+};
diff --git a/tools/testing/selftests/exec/nix_origin.bpf.c b/tools/testing/selftests/exec/nix_origin.bpf.c
new file mode 100644
index 000000000000..378e22a4c43b
--- /dev/null
+++ b/tools/testing/selftests/exec/nix_origin.bpf.c
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * nix_origin.bpf.c - $ORIGIN-relative PT_INTERP resolution
+ *
+ * A binfmt_misc_ops handler that makes relocatable (Nix-style) ELF
+ * binaries work: if PT_INTERP starts with "$ORIGIN/", the loader is
+ * resolved relative to the directory of the binary being executed and
+ * selected via bpf_binprm_set_interp(). The match program reads the
+ * program headers itself, so anything else never commits to this
+ * handler and passes through untouched.
+ *
+ * Activate with:
+ * bpftool struct_ops register nix_origin.bpf.o /sys/fs/bpf
+ * echo ':nix-origin:B::::nix_origin:' > /proc/sys/fs/binfmt_misc/register
+ */
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+#define PATH_MAX 4096
+#define EI_CLASS 4
+#define ELFCLASSXX 2 /* ELFCLASS64; flip to 1 for 32-bit */
+#define PT_INTERP 3
+#define MAX_PHDRS 64
+
+#define ORIGIN "$ORIGIN"
+#define ORIGIN_LEN (sizeof(ORIGIN) - 1)
+
+#define ENOENT 2
+#define ENOEXEC 8
+#define ENAMETOOLONG 36
+
+extern int bpf_dynptr_from_file(struct file *file, __u32 flags,
+ struct bpf_dynptr *ptr__uninit) __ksym;
+extern int bpf_dynptr_file_discard(struct bpf_dynptr *dynptr) __ksym;
+extern int bpf_path_d_path(const struct path *path, char *buf,
+ size_t buf__sz) __ksym;
+extern int bpf_binprm_set_interp(struct linux_binprm *bprm, const char *path,
+ size_t path__sz) __ksym;
+
+struct scratch {
+ char interp[PATH_MAX]; /* PT_INTERP as embedded in the binary */
+ char path[PATH_MAX]; /* d_path of the binary, becomes the result */
+};
+
+/* Keyed by pid: execs run concurrently and the programs can sleep. */
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(max_entries, 512);
+ __type(key, __u64);
+ __type(value, struct scratch);
+} scratch_map SEC(".maps");
+
+static const struct scratch zero_scratch;
+
+/* An ELF64 binary per the prefetched header? */
+static bool is_elf64(struct linux_binprm *bprm)
+{
+ return bprm->buf[0] == 0x7f && bprm->buf[1] == 'E' &&
+ bprm->buf[2] == 'L' && bprm->buf[3] == 'F' &&
+ bprm->buf[EI_CLASS] == ELFCLASSXX;
+}
+
+/* Locate PT_INTERP; false if the file has none or looks malformed. */
+static bool find_pt_interp(struct bpf_dynptr *dp, struct elf64_phdr *phdr)
+{
+ struct elf64_hdr ehdr;
+ bool found = false;
+ int i;
+
+ if (bpf_dynptr_read(&ehdr, sizeof(ehdr), dp, 0, 0))
+ return false;
+ if (ehdr.e_phentsize != sizeof(struct elf64_phdr))
+ return false;
+
+ bpf_for(i, 0, ehdr.e_phnum) {
+ if (i >= MAX_PHDRS)
+ break;
+ if (bpf_dynptr_read(phdr, sizeof(*phdr), dp,
+ ehdr.e_phoff + i * sizeof(*phdr), 0))
+ return false;
+ if (phdr->p_type == PT_INTERP) {
+ found = true;
+ break;
+ }
+ }
+ return found;
+}
+
+/*
+ * An ELF64 binary whose PT_INTERP starts with "$ORIGIN/" is ours. The
+ * match can sleep and read the file, so the decision is made here and
+ * regular binaries never commit to this handler: later binfmt_misc
+ * entries and binfmt_elf see them as if we did not exist.
+ */
+SEC("struct_ops.s/match")
+bool BPF_PROG(nix_origin_match, struct linux_binprm *bprm)
+{
+ char prefix[ORIGIN_LEN + 1] = {};
+ struct elf64_phdr phdr;
+ struct bpf_dynptr dp;
+ bool ours = false;
+
+ if (!is_elf64(bprm))
+ return false;
+
+ /* The dynptr must be discarded on every path once requested. */
+ if (bpf_dynptr_from_file(bprm->file, 0, &dp))
+ goto out;
+ if (find_pt_interp(&dp, &phdr) &&
+ phdr.p_filesz > ORIGIN_LEN + 1 &&
+ !bpf_dynptr_read(prefix, sizeof(prefix), &dp, phdr.p_offset, 0))
+ ours = !bpf_strncmp(prefix, sizeof(prefix), ORIGIN "/");
+out:
+ bpf_dynptr_file_discard(&dp);
+ return ours;
+}
+
+/*
+ * The match is committed and already vetted the "$ORIGIN/" prefix, so
+ * everything here reads the file again from scratch: -ENOEXEC only
+ * covers a binary that changed under us and stopped being ours.
+ */
+SEC("struct_ops.s/load")
+int BPF_PROG(nix_origin_load, struct linux_binprm *bprm)
+{
+ __u32 isz, sfx, rsz, slash;
+ struct elf64_phdr phdr;
+ struct bpf_dynptr dp;
+ struct scratch *sc;
+ __u64 id;
+ int ret = -ENOEXEC, len, i;
+
+ if (bpf_dynptr_from_file(bprm->file, 0, &dp))
+ goto out;
+
+ if (!find_pt_interp(&dp, &phdr))
+ goto out;
+
+ isz = phdr.p_filesz;
+ if (isz <= ORIGIN_LEN + 1 || isz >= sizeof(sc->interp))
+ goto out;
+ /*
+ * The range check above compiles to a test on a zero-extended copy of
+ * the u64 p_filesz, so the verifier does not carry the bound to the
+ * dynptr_read() length below ("unbounded memory access"). Mask isz to
+ * the buffer size (a power of two) and force the masked value to be
+ * materialized with a barrier so the read uses the bounded register.
+ */
+ isz &= sizeof(sc->interp) - 1;
+ barrier_var(isz);
+
+ id = bpf_get_current_pid_tgid();
+ if (bpf_map_update_elem(&scratch_map, &id, &zero_scratch, BPF_ANY))
+ goto out;
+ sc = bpf_map_lookup_elem(&scratch_map, &id);
+ if (!sc)
+ goto out_del;
+
+ if (bpf_dynptr_read(sc->interp, isz, &dp, phdr.p_offset, 0))
+ goto out_del;
+ if (sc->interp[isz - 1] != '\0')
+ goto out_del;
+
+ /* Not "$ORIGIN/..." anymore? Then it is not ours anymore either. */
+ if (sc->interp[0] != '$' || sc->interp[1] != 'O' ||
+ sc->interp[2] != 'R' || sc->interp[3] != 'I' ||
+ sc->interp[4] != 'G' || sc->interp[5] != 'I' ||
+ sc->interp[6] != 'N' || sc->interp[7] != '/')
+ goto out_del;
+
+ /*
+ * From here on resolution failures fail the exec instead of falling
+ * back to binfmt_elf, which would resolve the literal "$ORIGIN/..."
+ * relative to the caller's cwd.
+ */
+ ret = -ENOENT;
+ len = bpf_path_d_path(&bprm->file->f_path, sc->path, sizeof(sc->path));
+ if (len <= 0 || len > sizeof(sc->path))
+ goto out_del;
+ /* Unreachable or unlinked ("... (deleted)") binaries can't resolve. */
+ if (sc->path[0] != '/')
+ goto out_del;
+
+ /* $ORIGIN = dirname of the binary. */
+ slash = 0;
+ bpf_for(i, 1, len - 1) {
+ if (i >= sizeof(sc->path))
+ break;
+ if (sc->path[i] == '/')
+ slash = i;
+ }
+
+ /* Splice the suffix (leading '/' and NUL included) onto the dir. */
+ sfx = isz - ORIGIN_LEN;
+ rsz = slash + sfx;
+ if (rsz > sizeof(sc->path)) {
+ ret = -ENAMETOOLONG;
+ goto out_del;
+ }
+ bpf_for(i, 0, sfx) {
+ __u32 s = ORIGIN_LEN + i, d = slash + i;
+
+ if (s >= sizeof(sc->interp) || d >= sizeof(sc->path))
+ break;
+ sc->path[d] = sc->interp[s];
+ }
+
+ ret = bpf_binprm_set_interp(bprm, sc->path, rsz);
+out_del:
+ bpf_map_delete_elem(&scratch_map, &id);
+out:
+ bpf_dynptr_file_discard(&dp);
+ return ret;
+}
+
+SEC(".struct_ops.link")
+struct binfmt_misc_ops nix_origin = {
+ .match = (void *)nix_origin_match,
+ .load = (void *)nix_origin_load,
+ .name = "nix_origin",
+};
diff --git a/tools/testing/selftests/exec/transparent.bpf.c b/tools/testing/selftests/exec/transparent.bpf.c
new file mode 100644
index 000000000000..7632019ebe69
--- /dev/null
+++ b/tools/testing/selftests/exec/transparent.bpf.c
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * binfmt_misc_ops handler for the transparent-mode case: match a synthetic
+ * riscv ELF header and run the asserting interpreter transparently - the
+ * argument vector untouched, the binary in AT_EXECFD and mm->exe_file
+ * labeled with the binary.
+ */
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+#define EI_CLASS 4
+#define ELFCLASS64 2
+#define EM_RISCV 243
+
+extern int bpf_binprm_set_interp(struct linux_binprm *bprm, const char *path,
+ size_t path__sz) __ksym;
+extern int bpf_binprm_set_flags(struct linux_binprm *bprm,
+ enum bpf_binprm_flags flags) __ksym;
+
+SEC("struct_ops.s/match")
+bool BPF_PROG(transparent_match, struct linux_binprm *bprm)
+{
+ __u16 machine;
+
+ if (bprm->buf[0] != 0x7f || bprm->buf[1] != 'E' ||
+ bprm->buf[2] != 'L' || bprm->buf[3] != 'F' ||
+ bprm->buf[EI_CLASS] != ELFCLASS64)
+ return false;
+
+ /* e_machine is a 16-bit little-endian field at offset 18. */
+ machine = (__u8)bprm->buf[18] | ((__u16)(__u8)bprm->buf[19] << 8);
+ return machine == EM_RISCV;
+}
+
+SEC("struct_ops.s/load")
+int BPF_PROG(transparent_load, struct linux_binprm *bprm)
+{
+ char interp[] = "/tmp/binfmt_transparent_interp";
+ int err;
+
+ err = bpf_binprm_set_flags(bprm, BPF_BINPRM_TRANSPARENT);
+ if (err)
+ return err;
+
+ /* @path__sz includes the terminating NUL; 0 commits the selection. */
+ return bpf_binprm_set_interp(bprm, interp, sizeof(interp));
+}
+
+SEC(".struct_ops.link")
+struct binfmt_misc_ops transparent = {
+ .match = (void *)transparent_match,
+ .load = (void *)transparent_load,
+ .name = "transparent",
+};