diff options
| author | Christian Brauner <brauner@kernel.org> | 2026-07-30 15:34:09 +0200 |
|---|---|---|
| committer | Christian Brauner <brauner@kernel.org> | 2026-08-03 19:15:44 +0200 |
| commit | 6ec7c96bee30a7d9f3982951aa19f712feb14f6a (patch) | |
| tree | 3fdc2d9f4a9b24313823e8375e049907be446a08 /include/linux | |
| parent | a7b880449ab1b2389b31ed9da703691dcac54655 (diff) | |
binfmt_misc: let a 'B' entry bind its interpreters
A 'B' entry's load program selects its interpreter by absolute path,
which open_exec() resolves at exec time in the mount namespace of whoever
runs the binary. The handler names an interpreter but does not get to say
which file that is. Whoever controls the filesystem view of the exec
decides that instead.
Static entries settled this long ago with 'F'. The interpreter is opened
at registration in the registrant's context and every exec runs a clone
of that file. Give a 'B' entry the same, for as many interpreters as it
needs.
An entry registered with 'D' cannot be matched yet, so it still belongs
to whoever is configuring it and can be given interpreters one write at a
time:
echo ':qemu:B::::qemu_user:D' > register
echo '+aarch64 /usr/bin/qemu-aarch64' > qemu
echo '+arm /usr/bin/qemu-arm' > qemu
echo 1 > qemu
Each path is opened by its write, with the credentials the entry file
was opened with, by the same helper that opens an 'F' interpreter. The
load program picks one per exec with bpf_binprm_select_interp() and the
entry hands out a clone of it. Nothing is resolved again, in any
namespace. The path is everything past the first space, so no
interpreter has to fit in a register string. An entry binds at most a
hundred interpreters (BINFMT_MISC_INTERP_MAX). Every binding pins a
struct file that no file descriptor accounts for, so RLIMIT_NOFILE does
not apply and some cap is needed. A hundred is plenty and raising it
later is cheap, lowering it is not.
Selection is by name so the register string and the program need not
agree on an order, and so the handler is not tied to where a distribution
puts its interpreters. A name is a single word of printable ASCII so the
entry file can report 'name path' lines. The interpreter runs under the
path it was registered under.
The entry file reads user memory once. bm_entry_write() copies the write
in and dispatches on the first byte, and parse_command() takes the copied
buffer. The status file has no binding to spell, so it keeps its own
small copy in read_command().
That moves the length cap ahead of the dispatch. A write to an entry file
longer than a binding can be is now refused with -E2BIG, and one from a
bad address reports -EFAULT, where the command parser used to report
-EINVAL for anything past three bytes.
Configurations of one instance are kept apart by the lock removal
already takes. Reading the set out of the entry file takes no lock.
Bindings are rcu-published and the open entry file pins the entry
together with everything it bound, so a reader either sees a whole node
or misses it. The interpreter is opened before the configuration lock
because resolving the path may walk this very filesystem, and only
after the command has been parsed and the name validated from the
copied buffer, so a write that can never bind opens nothing and the
errno reflects the actual failure.
Link: https://patch.msgid.link/20260730-work-binfmt_misc-preopen-v1-7-4a0b0da71f16@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Diffstat (limited to 'include/linux')
| -rw-r--r-- | include/linux/binfmt_misc.h | 39 | ||||
| -rw-r--r-- | include/linux/binfmts.h | 3 |
2 files changed, 38 insertions, 4 deletions
diff --git a/include/linux/binfmt_misc.h b/include/linux/binfmt_misc.h index 4abdfd36b3fa..072e4b3dd78d 100644 --- a/include/linux/binfmt_misc.h +++ b/include/linux/binfmt_misc.h @@ -5,11 +5,41 @@ #include <linux/types.h> struct bpf_prog; +struct file; struct linux_binprm; 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 + * @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; + 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) @@ -42,10 +72,11 @@ enum bpf_binprm_flags { * 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() 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 + * 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 { diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h index a2daecbb01d6..f686a37f7a0a 100644 --- a/include/linux/binfmts.h +++ b/include/linux/binfmts.h @@ -14,7 +14,10 @@ struct coredump_params; /* 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 */ }; |
