summaryrefslogtreecommitdiff
path: root/include/linux
diff options
context:
space:
mode:
authorChristian Brauner <brauner@kernel.org>2026-07-30 15:34:09 +0200
committerChristian Brauner <brauner@kernel.org>2026-08-03 19:15:44 +0200
commit6ec7c96bee30a7d9f3982951aa19f712feb14f6a (patch)
tree3fdc2d9f4a9b24313823e8375e049907be446a08 /include/linux
parenta7b880449ab1b2389b31ed9da703691dcac54655 (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.h39
-rw-r--r--include/linux/binfmts.h3
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 */
};