| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/08 06:00 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"BINFMT_MISC_BPF"
],
"FocusSymbols": [
"bpf_binprm_set_interp",
"bm_bpf_reg",
"bm_bpf_unreg",
"create_entry",
"bm_register_write"
],
"Reasoning": "The patch introduces a new feature allowing BPF programs to act as handlers for binfmt_misc. It adds new BPF struct_ops and kfuncs, and modifies the binfmt_misc registration and execution logic. Fuzzing should focus on the BPF registration/unregistration, the new kfunc, and the binfmt_misc registration path.",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/08 06:00 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit a6912d09464c93f365054dd673c2ad197f3054f3\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Wed Jul 8 06:00:26 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/admin-guide/binfmt-misc.rst b/Documentation/admin-guide/binfmt-misc.rst\nindex c0a34fbf8022b5..c0efb077362843 100644\n--- a/Documentation/admin-guide/binfmt-misc.rst\n+++ b/Documentation/admin-guide/binfmt-misc.rst\n@@ -26,11 +26,13 @@ Here is what the fields mean:\n name below ``/proc/sys/fs/binfmt_misc``; cannot contain slashes ``/`` for\n obvious reasons.\n - ``type``\n- is the type of recognition. Give ``M`` for magic and ``E`` for extension.\n+ is the type of recognition. Give ``M`` for magic, ``E`` for extension and\n+ ``B`` for a bpf-backed handler (see below).\n - ``offset``\n is the offset of the magic/mask in the file, counted in bytes. This\n defaults to 0 if you omit it (i.e. you write ``:name:type::magic...``).\n- Ignored when using filename extension matching.\n+ Ignored when using filename extension matching. For ``B`` entries this\n+ field carries the name of the bpf handler instead.\n - ``magic``\n is the byte sequence binfmt_misc is matching for. The magic string\n may contain hex-encoded characters like ``\\x0a`` or ``\\xA4``. Note that you\n@@ -97,6 +99,40 @@ There are some restrictions:\n offset+size(magic) has to be less than 128\n - the interpreter string may not exceed 127 characters\n \n+\n+bpf-backed handlers\n+-------------------\n+\n+With ``CONFIG_BINFMT_MISC_BPF`` both the matching and the interpreter\n+selection can be delegated to a bpf program. A handler is an instance of the\n+``binfmt_misc_ops`` struct_ops with a sleepable ``load`` program and a\n+``name``. Once the struct_ops map is registered the handler can be activated\n+with a ``B`` entry that references it by name and carries neither magic,\n+mask, nor interpreter::\n+\n+ echo ':qemu:B:my_handler::::' \u003e register\n+\n+At exec time the ``load`` program receives the ``linux_binprm`` of the\n+binary. It can match on the header in ``bprm-\u003ebuf``, read the file itself,\n+e.g. to parse ELF program headers, and derive the interpreter from the\n+binary's location. It selects the interpreter by calling the\n+``bpf_binprm_set_interp()`` kfunc with an absolute path and returning a\n+positive value. Returning ``0`` falls through to the handlers registered\n+after this one, a negative errno fails the exec with that error;\n+``-ENOEXEC`` ends the binfmt_misc search but lets the remaining binary\n+formats have a go. The interpreter is opened with the credentials of the\n+task doing the exec, exactly as a statically registered interpreter would\n+be.\n+\n+Handlers are looked up in the user namespace the struct_ops map was\n+registered in, falling back to ancestor namespaces, mirroring how\n+binfmt_misc instances themselves are looked up. The entry keeps the handler\n+alive; deleting the struct_ops map only prevents new registrations.\n+\n+The ``C`` and ``F`` flags cannot be combined with ``B`` entries: there is no\n+fixed interpreter to pre-open and a program-selected interpreter must never\n+inherit the credentials of a setuid binary.\n+\n To use binfmt_misc you have to mount it first. You can mount it with\n ``mount -t binfmt_misc none /proc/sys/fs/binfmt_misc`` command, or you can add\n a line ``none /proc/sys/fs/binfmt_misc binfmt_misc defaults 0 0`` to your\ndiff --git a/fs/Kconfig.binfmt b/fs/Kconfig.binfmt\nindex 1949e25c7741b1..daeac4889d03ef 100644\n--- a/fs/Kconfig.binfmt\n+++ b/fs/Kconfig.binfmt\n@@ -168,6 +168,20 @@ config BINFMT_MISC\n \t you have use for it; the module is called binfmt_misc. If you\n \t don't know what to answer at this point, say Y.\n \n+config BINFMT_MISC_BPF\n+\tbool \"BPF-selected interpreters for misc binaries\"\n+\tdepends on BINFMT_MISC=y\n+\tdepends on BPF_SYSCALL \u0026\u0026 BPF_JIT \u0026\u0026 DEBUG_INFO_BTF\n+\thelp\n+\t Allow binfmt_misc binary type handlers to be implemented as bpf\n+\t struct_ops programs. Instead of matching a fixed magic and\n+\t redirecting to a fixed interpreter recorded at registration time\n+\t such handlers match binaries programmatically and compute the\n+\t interpreter to use per binary, e.g. relative to the location of\n+\t the binary itself.\n+\n+\t If you don't know what to answer at this point, say N.\n+\n config COREDUMP\n \tbool \"Enable core dump support\" if EXPERT\n \tdefault y\ndiff --git a/fs/Makefile b/fs/Makefile\nindex 89a8a9d207d1c5..499c6670f0c183 100644\n--- a/fs/Makefile\n+++ b/fs/Makefile\n@@ -33,6 +33,7 @@ obj-$(CONFIG_FS_ENCRYPTION)\t+= crypto/\n obj-$(CONFIG_FS_VERITY)\t\t+= verity/\n obj-$(CONFIG_FILE_LOCKING) += locks.o\n obj-$(CONFIG_BINFMT_MISC)\t+= binfmt_misc.o\n+obj-$(CONFIG_BINFMT_MISC_BPF)\t+= binfmt_misc_bpf.o\n obj-$(CONFIG_BINFMT_SCRIPT)\t+= binfmt_script.o\n obj-$(CONFIG_BINFMT_ELF)\t+= binfmt_elf.o\n obj-$(CONFIG_COMPAT_BINFMT_ELF)\t+= compat_binfmt_elf.o\ndiff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c\nindex 84349fcb93f13a..b80e219ba5876b 100644\n--- a/fs/binfmt_misc.c\n+++ b/fs/binfmt_misc.c\n@@ -17,6 +17,7 @@\n #include \u003clinux/sched/mm.h\u003e\n #include \u003clinux/magic.h\u003e\n #include \u003clinux/binfmts.h\u003e\n+#include \u003clinux/binfmt_misc.h\u003e\n #include \u003clinux/slab.h\u003e\n #include \u003clinux/ctype.h\u003e\n #include \u003clinux/string_helpers.h\u003e\n@@ -41,7 +42,7 @@ enum {\n \tVERBOSE_STATUS = 1 /* make it zero to save 400 bytes kernel memory */\n };\n \n-enum {Enabled, Magic};\n+enum {Enabled, Magic, Bpf};\n #define MISC_FMT_PRESERVE_ARGV0 (1UL \u003c\u003c 31)\n #define MISC_FMT_OPEN_BINARY (1UL \u003c\u003c 30)\n #define MISC_FMT_CREDENTIALS (1UL \u003c\u003c 29)\n@@ -58,6 +59,8 @@ typedef struct {\n \tchar *name;\n \tstruct dentry *dentry;\n \tstruct file *interp_file;\n+\tconst struct binfmt_misc_ops *bpf_ops;\t/* bpf-backed handler ('B') */\n+\tconst char *bpf_ops_name;\n \trefcount_t users;\t\t/* sync removal with load_misc_binary() */\n } Node;\n \n@@ -82,14 +85,19 @@ static struct file_system_type bm_fs_type;\n * search_binfmt_handler - search for a binary handler for @bprm\n * @misc: handle to binfmt_misc instance\n * @bprm: binary for which we are looking for a handler\n+ * @bpf_skip: number of bpf-backed handlers to skip over\n *\n * Search for a binary type handler for @bprm in the list of registered binary\n- * type handlers.\n+ * type handlers. A bpf-backed handler cannot be matched here as its program\n+ * must run in sleepable context; it is returned as a candidate and the\n+ * program decides in load_misc_binary(). @bpf_skip allows the caller to\n+ * resume the search after the first @bpf_skip candidates declined.\n *\n * Return: binary type list entry on success, NULL on failure\n */\n static Node *search_binfmt_handler(struct binfmt_misc *misc,\n-\t\t\t\t struct linux_binprm *bprm)\n+\t\t\t\t struct linux_binprm *bprm,\n+\t\t\t\t unsigned int bpf_skip)\n {\n \tchar *p = strrchr(bprm-\u003einterp, '.');\n \tNode *e;\n@@ -103,6 +111,15 @@ static Node *search_binfmt_handler(struct binfmt_misc *misc,\n \t\tif (!test_bit(Enabled, \u0026e-\u003eflags))\n \t\t\tcontinue;\n \n+\t\t/* The program decides in load_misc_binary(). */\n+\t\tif (test_bit(Bpf, \u0026e-\u003eflags)) {\n+\t\t\tif (bpf_skip) {\n+\t\t\t\tbpf_skip--;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\treturn e;\n+\t\t}\n+\n \t\t/* Do matching based on extension if applicable. */\n \t\tif (!test_bit(Magic, \u0026e-\u003eflags)) {\n \t\t\tif (p \u0026\u0026 !strcmp(e-\u003emagic, p + 1))\n@@ -132,6 +149,7 @@ static Node *search_binfmt_handler(struct binfmt_misc *misc,\n * get_binfmt_handler - try to find a binary type handler\n * @misc: handle to binfmt_misc instance\n * @bprm: binary for which we are looking for a handler\n+ * @bpf_skip: number of bpf-backed handlers to skip over\n *\n * Try to find a binfmt handler for the binary type. If one is found take a\n * reference to protect against removal via bm_{entry,status}_write().\n@@ -139,12 +157,13 @@ static Node *search_binfmt_handler(struct binfmt_misc *misc,\n * Return: binary type list entry on success, NULL on failure\n */\n static Node *get_binfmt_handler(struct binfmt_misc *misc,\n-\t\t\t\tstruct linux_binprm *bprm)\n+\t\t\t\tstruct linux_binprm *bprm,\n+\t\t\t\tunsigned int bpf_skip)\n {\n \tNode *e;\n \n \tread_lock(\u0026misc-\u003eentries_lock);\n-\te = search_binfmt_handler(misc, bprm);\n+\te = search_binfmt_handler(misc, bprm, bpf_skip);\n \tif (e)\n \t\trefcount_inc(\u0026e-\u003eusers);\n \tread_unlock(\u0026misc-\u003eentries_lock);\n@@ -164,6 +183,8 @@ static void put_binfmt_handler(Node *e)\n \tif (refcount_dec_and_test(\u0026e-\u003eusers)) {\n \t\tif (e-\u003eflags \u0026 MISC_FMT_OPEN_FILE)\n \t\t\tfilp_close(e-\u003einterp_file, NULL);\n+\t\tif (e-\u003ebpf_ops)\n+\t\t\tbinfmt_misc_put_ops(e-\u003ebpf_ops);\n \t\tkfree(e);\n \t}\n }\n@@ -206,12 +227,15 @@ static int load_misc_binary(struct linux_binprm *bprm)\n \tstruct file *interp_file = NULL;\n \tint retval = -ENOEXEC;\n \tstruct binfmt_misc *misc;\n+\tconst char *interpreter;\n+\tunsigned int bpf_skip = 0;\n \n \tmisc = load_binfmt_misc();\n \tif (!misc-\u003eenabled)\n \t\treturn retval;\n \n-\tfmt = get_binfmt_handler(misc, bprm);\n+retry:\n+\tfmt = get_binfmt_handler(misc, bprm, bpf_skip);\n \tif (!fmt)\n \t\treturn retval;\n \n@@ -220,6 +244,32 @@ static int load_misc_binary(struct linux_binprm *bprm)\n \tif (bprm-\u003einterp_flags \u0026 BINPRM_FLAGS_PATH_INACCESSIBLE)\n \t\tgoto ret;\n \n+\tif (test_bit(Bpf, \u0026fmt-\u003eflags)) {\n+\t\tretval = fmt-\u003ebpf_ops-\u003eload(bprm);\n+\t\tif (retval \u003c 0) {\n+\t\t\t/* Keep a program-supplied error within errno range. */\n+\t\t\tif (retval \u003c -MAX_ERRNO)\n+\t\t\t\tretval = -ENOEXEC;\n+\t\t\tgoto ret;\n+\t\t}\n+\t\tif (!retval) {\n+\t\t\t/* Declined, move on to later handlers. */\n+\t\t\tkfree(bprm-\u003ebpf_interp);\n+\t\t\tbprm-\u003ebpf_interp = NULL;\n+\t\t\tput_binfmt_handler(fmt);\n+\t\t\tbpf_skip++;\n+\t\t\tretval = -ENOEXEC;\n+\t\t\tgoto retry;\n+\t\t}\n+\t\t/* Selecting an interpreter is part of the contract. */\n+\t\tretval = -ENOEXEC;\n+\t\tif (!bprm-\u003ebpf_interp)\n+\t\t\tgoto ret;\n+\t\tinterpreter = bprm-\u003ebpf_interp;\n+\t} else {\n+\t\tinterpreter = fmt-\u003einterpreter;\n+\t}\n+\n \tif (fmt-\u003eflags \u0026 MISC_FMT_PRESERVE_ARGV0) {\n \t\tbprm-\u003einterp_flags |= BINPRM_FLAGS_PRESERVE_ARGV0;\n \t} else {\n@@ -238,13 +288,13 @@ static int load_misc_binary(struct linux_binprm *bprm)\n \tbprm-\u003eargc++;\n \n \t/* add the interp as argv[0] */\n-\tretval = copy_string_kernel(fmt-\u003einterpreter, bprm);\n+\tretval = copy_string_kernel(interpreter, bprm);\n \tif (retval \u003c 0)\n \t\tgoto ret;\n \tbprm-\u003eargc++;\n \n \t/* Update interp in case binfmt_script needs it. */\n-\tretval = bprm_change_interp(fmt-\u003einterpreter, bprm);\n+\tretval = bprm_change_interp(interpreter, bprm);\n \tif (retval \u003c 0)\n \t\tgoto ret;\n \n@@ -253,7 +303,7 @@ static int load_misc_binary(struct linux_binprm *bprm)\n \t\tif (!IS_ERR(interp_file))\n \t\t\tdeny_write_access(interp_file);\n \t} else {\n-\t\tinterp_file = open_exec(fmt-\u003einterpreter);\n+\t\tinterp_file = open_exec(interpreter);\n \t}\n \tretval = PTR_ERR(interp_file);\n \tif (IS_ERR(interp_file))\n@@ -265,6 +315,9 @@ static int load_misc_binary(struct linux_binprm *bprm)\n \n \tretval = 0;\n ret:\n+\t/* A program-selected interpreter is consumed by this exec attempt. */\n+\tkfree(bprm-\u003ebpf_interp);\n+\tbprm-\u003ebpf_interp = NULL;\n \n \t/*\n \t * If we actually put the node here all concurrent calls to\n@@ -404,13 +457,47 @@ static Node *create_entry(const char __user *buffer, size_t count)\n \t\tpr_debug(\"register: type: M (magic)\\n\");\n \t\te-\u003eflags = (1 \u003c\u003c Enabled) | (1 \u003c\u003c Magic);\n \t\tbreak;\n+\tcase 'B':\n+\t\tif (!IS_ENABLED(CONFIG_BINFMT_MISC_BPF))\n+\t\t\tgoto einval;\n+\t\tpr_debug(\"register: type: B (bpf)\\n\");\n+\t\te-\u003eflags = (1 \u003c\u003c Enabled) | (1 \u003c\u003c Bpf);\n+\t\tbreak;\n \tdefault:\n \t\tgoto einval;\n \t}\n \tif (*p++ != del)\n \t\tgoto einval;\n \n-\tif (test_bit(Magic, \u0026e-\u003eflags)) {\n+\tif (test_bit(Bpf, \u0026e-\u003eflags)) {\n+\t\tchar *s;\n+\n+\t\t/* The 'offset' field carries the handler name. */\n+\t\ts = strchr(p, del);\n+\t\tif (!s)\n+\t\t\tgoto einval;\n+\t\t*s++ = '\\0';\n+\t\te-\u003ebpf_ops_name = p;\n+\t\tif (!e-\u003ebpf_ops_name[0] ||\n+\t\t strlen(e-\u003ebpf_ops_name) \u003e= BINFMT_MISC_OPS_NAME_MAX)\n+\t\t\tgoto einval;\n+\t\tp = s;\n+\t\tpr_debug(\"register: bpf handler: {%s}\\n\", e-\u003ebpf_ops_name);\n+\n+\t\t/* The 'magic' field must be empty. */\n+\t\ts = strchr(p, del);\n+\t\tif (!s || s != p)\n+\t\t\tgoto einval;\n+\t\t*s++ = '\\0';\n+\t\tp = s;\n+\n+\t\t/* The 'mask' field must be empty. */\n+\t\ts = strchr(p, del);\n+\t\tif (!s || s != p)\n+\t\t\tgoto einval;\n+\t\t*s++ = '\\0';\n+\t\tp = s;\n+\t} else if (test_bit(Magic, \u0026e-\u003eflags)) {\n \t\t/* Handle the 'M' (magic) format. */\n \t\tchar *s;\n \n@@ -524,8 +611,13 @@ static Node *create_entry(const char __user *buffer, size_t count)\n \tif (!p)\n \t\tgoto einval;\n \t*p++ = '\\0';\n-\tif (!e-\u003einterpreter[0])\n+\tif (test_bit(Bpf, \u0026e-\u003eflags)) {\n+\t\t/* The program selects the interpreter at exec time. */\n+\t\tif (e-\u003einterpreter[0])\n+\t\t\tgoto einval;\n+\t} else if (!e-\u003einterpreter[0]) {\n \t\tgoto einval;\n+\t}\n \tpr_debug(\"register: interpreter: {%s}\\n\", e-\u003einterpreter);\n \n \t/* Parse the 'flags' field. */\n@@ -535,6 +627,14 @@ static Node *create_entry(const char __user *buffer, size_t count)\n \tif (p != buf + count)\n \t\tgoto einval;\n \n+\t/*\n+\t * A program-selected interpreter cannot be pre-opened and must not\n+\t * inherit the credentials of a setuid binary it was chosen for.\n+\t */\n+\tif (test_bit(Bpf, \u0026e-\u003eflags) \u0026\u0026\n+\t (e-\u003eflags \u0026 (MISC_FMT_CREDENTIALS | MISC_FMT_OPEN_FILE)))\n+\t\tgoto einval;\n+\n \treturn e;\n \n out:\n@@ -588,7 +688,10 @@ static void entry_status(Node *e, char *page)\n \t\treturn;\n \t}\n \n-\tdp += sprintf(dp, \"%s\\ninterpreter %s\\n\", status, e-\u003einterpreter);\n+\tif (test_bit(Bpf, \u0026e-\u003eflags))\n+\t\tdp += sprintf(dp, \"%s\\nbpf %s\\n\", status, e-\u003ebpf_ops_name);\n+\telse\n+\t\tdp += sprintf(dp, \"%s\\ninterpreter %s\\n\", status, e-\u003einterpreter);\n \n \t/* print the special flags */\n \tdp += sprintf(dp, \"flags: \");\n@@ -602,7 +705,9 @@ static void entry_status(Node *e, char *page)\n \t\t*dp++ = 'F';\n \t*dp++ = '\\n';\n \n-\tif (!test_bit(Magic, \u0026e-\u003eflags)) {\n+\tif (test_bit(Bpf, \u0026e-\u003eflags)) {\n+\t\t*dp = '\\0';\n+\t} else if (!test_bit(Magic, \u0026e-\u003eflags)) {\n \t\tsprintf(dp, \"extension .%s\\n\", e-\u003emagic);\n \t} else {\n \t\tdp += sprintf(dp, \"offset %i\\nmagic \", e-\u003eoffset);\n@@ -809,6 +914,16 @@ static ssize_t bm_register_write(struct file *file, const char __user *buffer,\n \tif (IS_ERR(e))\n \t\treturn PTR_ERR(e);\n \n+\tif (test_bit(Bpf, \u0026e-\u003eflags)) {\n+\t\te-\u003ebpf_ops = binfmt_misc_get_ops(sb-\u003es_user_ns, e-\u003ebpf_ops_name);\n+\t\tif (!e-\u003ebpf_ops) {\n+\t\t\tpr_notice(\"register: no bpf handler named %s\\n\",\n+\t\t\t\t e-\u003ebpf_ops_name);\n+\t\t\tkfree(e);\n+\t\t\treturn -ENOENT;\n+\t\t}\n+\t}\n+\n \tif (e-\u003eflags \u0026 MISC_FMT_OPEN_FILE) {\n \t\t/*\n \t\t * Now that we support unprivileged binfmt_misc mounts make\n@@ -834,6 +949,8 @@ static ssize_t bm_register_write(struct file *file, const char __user *buffer,\n \t\t\texe_file_allow_write_access(f);\n \t\t\tfilp_close(f, NULL);\n \t\t}\n+\t\tif (e-\u003ebpf_ops)\n+\t\t\tbinfmt_misc_put_ops(e-\u003ebpf_ops);\n \t\tkfree(e);\n \t\treturn err;\n \t}\ndiff --git a/fs/binfmt_misc_bpf.c b/fs/binfmt_misc_bpf.c\nnew file mode 100644\nindex 00000000000000..72da0964d94915\n--- /dev/null\n+++ b/fs/binfmt_misc_bpf.c\n@@ -0,0 +1,275 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * BPF-backed binary type handlers for binfmt_misc.\n+ *\n+ * A handler is a struct binfmt_misc_ops struct_ops map. Loading and\n+ * registering it makes the handler available under its name in the user\n+ * namespace it was registered in. A binfmt_misc 'B' entry activates it:\n+ *\n+ * echo ':entry:B:\u003chandler-name\u003e::::' \u003e \u003cbinfmt_misc\u003e/register\n+ */\n+\n+#include \u003clinux/binfmt_misc.h\u003e\n+#include \u003clinux/binfmts.h\u003e\n+#include \u003clinux/bpf.h\u003e\n+#include \u003clinux/bpf_verifier.h\u003e\n+#include \u003clinux/btf.h\u003e\n+#include \u003clinux/btf_ids.h\u003e\n+#include \u003clinux/cred.h\u003e\n+#include \u003clinux/init.h\u003e\n+#include \u003clinux/limits.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/string.h\u003e\n+#include \u003clinux/user_namespace.h\u003e\n+\n+struct bm_bpf_ops_reg {\n+\tstruct list_head list;\n+\tconst struct binfmt_misc_ops *ops;\n+\tstruct bpf_link *link;\n+\tstruct user_namespace *user_ns;\n+};\n+\n+static DEFINE_MUTEX(bm_bpf_ops_lock);\n+static LIST_HEAD(bm_bpf_ops_list);\n+\n+static struct bpf_struct_ops bpf_binfmt_misc_ops;\n+\n+static struct bm_bpf_ops_reg *bm_bpf_ops_find(const struct user_namespace *user_ns,\n+\t\t\t\t\t const char *name)\n+{\n+\tstruct bm_bpf_ops_reg *reg;\n+\n+\tlockdep_assert_held(\u0026bm_bpf_ops_lock);\n+\n+\tlist_for_each_entry(reg, \u0026bm_bpf_ops_list, list) {\n+\t\tif (reg-\u003euser_ns == user_ns \u0026\u0026 !strcmp(reg-\u003eops-\u003ename, name))\n+\t\t\treturn reg;\n+\t}\n+\treturn NULL;\n+}\n+\n+/**\n+ * binfmt_misc_get_ops - look up a bpf binary type handler by name\n+ * @user_ns: user namespace of the binfmt_misc instance\n+ * @name: name the handler was registered under\n+ *\n+ * Search @user_ns and its ancestors for a handler named @name, mirroring\n+ * the instance lookup in load_binfmt_misc(). The returned handler stays\n+ * callable until binfmt_misc_put_ops() even if the backing struct_ops map\n+ * is detached or deleted in the meantime.\n+ *\n+ * Return: the handler on success, NULL on failure\n+ */\n+const struct binfmt_misc_ops *binfmt_misc_get_ops(struct user_namespace *user_ns,\n+\t\t\t\t\t\t const char *name)\n+{\n+\tconst struct user_namespace *ns;\n+\tstruct bm_bpf_ops_reg *reg;\n+\n+\tguard(mutex)(\u0026bm_bpf_ops_lock);\n+\n+\tfor (ns = user_ns; ns; ns = ns-\u003eparent) {\n+\t\treg = bm_bpf_ops_find(ns, name);\n+\t\tif (!reg)\n+\t\t\tcontinue;\n+\t\tif (!bpf_struct_ops_get(reg-\u003eops))\n+\t\t\treturn NULL;\n+\t\treturn reg-\u003eops;\n+\t}\n+\treturn NULL;\n+}\n+\n+void binfmt_misc_put_ops(const struct binfmt_misc_ops *ops)\n+{\n+\tbpf_struct_ops_put(ops);\n+}\n+\n+bool bpf_prog_is_binfmt_misc_ops(const struct bpf_prog *prog)\n+{\n+\treturn prog-\u003etype == BPF_PROG_TYPE_STRUCT_OPS \u0026\u0026\n+\t prog-\u003eaux-\u003est_ops == \u0026bpf_binfmt_misc_ops;\n+}\n+\n+__bpf_kfunc_start_defs();\n+\n+/**\n+ * bpf_binprm_set_interp - select the interpreter for the current exec\n+ * @bprm: binary that is being executed\n+ * @path: absolute path to the interpreter\n+ * @path__sz: size of the @path buffer, including the terminating NUL\n+ *\n+ * To be called from the load program of a struct binfmt_misc_ops handler\n+ * before returning a positive value. The path is opened with the\n+ * credentials of the task doing the exec after the program returns.\n+ *\n+ * Return: 0 on success, a negative errno on failure\n+ */\n+__bpf_kfunc int bpf_binprm_set_interp(struct linux_binprm *bprm,\n+\t\t\t\t const char *path, size_t path__sz)\n+{\n+\tsize_t len;\n+\tchar *interp;\n+\n+\tif (!path__sz)\n+\t\treturn -EINVAL;\n+\tlen = strnlen(path, path__sz);\n+\tif (len == path__sz)\n+\t\treturn -EINVAL;\n+\tif (path[0] != '/')\n+\t\treturn -EINVAL;\n+\tif (len \u003e= PATH_MAX)\n+\t\treturn -ENAMETOOLONG;\n+\n+\tinterp = kmemdup_nul(path, len, GFP_KERNEL);\n+\tif (!interp)\n+\t\treturn -ENOMEM;\n+\n+\tkfree(bprm-\u003ebpf_interp);\n+\tbprm-\u003ebpf_interp = interp;\n+\treturn 0;\n+}\n+\n+__bpf_kfunc_end_defs();\n+\n+BTF_KFUNCS_START(bm_bpf_kfunc_ids)\n+BTF_ID_FLAGS(func, bpf_binprm_set_interp, KF_SLEEPABLE)\n+BTF_KFUNCS_END(bm_bpf_kfunc_ids)\n+\n+static int bm_bpf_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id)\n+{\n+\tif (!btf_id_set8_contains(\u0026bm_bpf_kfunc_ids, kfunc_id))\n+\t\treturn 0;\n+\tif (bpf_prog_is_binfmt_misc_ops(prog))\n+\t\treturn 0;\n+\treturn -EACCES;\n+}\n+\n+static const struct btf_kfunc_id_set bm_bpf_kfunc_set = {\n+\t.owner\t= THIS_MODULE,\n+\t.set\t= \u0026bm_bpf_kfunc_ids,\n+\t.filter\t= bm_bpf_kfunc_filter,\n+};\n+\n+static int bm_bpf_ops__load(struct linux_binprm *bprm)\n+{\n+\treturn 0;\n+}\n+\n+static struct binfmt_misc_ops bm_bpf_ops_stubs = {\n+\t.load = bm_bpf_ops__load,\n+};\n+\n+static int bm_bpf_init(struct btf *btf)\n+{\n+\treturn register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS,\n+\t\t\t\t\t \u0026bm_bpf_kfunc_set);\n+}\n+\n+static int bm_bpf_check_member(const struct btf_type *t,\n+\t\t\t const struct btf_member *member,\n+\t\t\t const struct bpf_prog *prog)\n+{\n+\tu32 moff = __btf_member_bit_offset(t, member) / 8;\n+\n+\tswitch (moff) {\n+\tcase offsetof(struct binfmt_misc_ops, load):\n+\t\t/* Reliable file reads at exec time require sleeping. */\n+\t\tif (!prog-\u003esleepable)\n+\t\t\treturn -EINVAL;\n+\t\tbreak;\n+\t}\n+\treturn 0;\n+}\n+\n+static int bm_bpf_init_member(const struct btf_type *t,\n+\t\t\t const struct btf_member *member,\n+\t\t\t void *kdata, const void *udata)\n+{\n+\tconst struct binfmt_misc_ops *uops = udata;\n+\tstruct binfmt_misc_ops *ops = kdata;\n+\tu32 moff = __btf_member_bit_offset(t, member) / 8;\n+\n+\tswitch (moff) {\n+\tcase offsetof(struct binfmt_misc_ops, name):\n+\t\tif (bpf_obj_name_cpy(ops-\u003ename, uops-\u003ename,\n+\t\t\t\t sizeof(ops-\u003ename)) \u003c= 0)\n+\t\t\treturn -EINVAL;\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+static int bm_bpf_validate(void *kdata)\n+{\n+\tstruct binfmt_misc_ops *ops = kdata;\n+\n+\tif (!ops-\u003eload)\n+\t\treturn -EINVAL;\n+\treturn 0;\n+}\n+\n+static int bm_bpf_reg(void *kdata, struct bpf_link *link)\n+{\n+\tstruct binfmt_misc_ops *ops = kdata;\n+\tstruct bm_bpf_ops_reg *reg;\n+\n+\treg = kzalloc_obj(*reg, GFP_KERNEL_ACCOUNT);\n+\tif (!reg)\n+\t\treturn -ENOMEM;\n+\n+\treg-\u003eops = ops;\n+\treg-\u003elink = link;\n+\treg-\u003euser_ns = get_user_ns(current_user_ns());\n+\n+\tguard(mutex)(\u0026bm_bpf_ops_lock);\n+\n+\tif (bm_bpf_ops_find(reg-\u003euser_ns, ops-\u003ename)) {\n+\t\tput_user_ns(reg-\u003euser_ns);\n+\t\tkfree(reg);\n+\t\treturn -EEXIST;\n+\t}\n+\n+\tlist_add(\u0026reg-\u003elist, \u0026bm_bpf_ops_list);\n+\treturn 0;\n+}\n+\n+static void bm_bpf_unreg(void *kdata, struct bpf_link *link)\n+{\n+\tstruct bm_bpf_ops_reg *reg;\n+\n+\tguard(mutex)(\u0026bm_bpf_ops_lock);\n+\n+\tlist_for_each_entry(reg, \u0026bm_bpf_ops_list, list) {\n+\t\tif (reg-\u003eops == kdata \u0026\u0026 reg-\u003elink == link) {\n+\t\t\tlist_del(\u0026reg-\u003elist);\n+\t\t\tput_user_ns(reg-\u003euser_ns);\n+\t\t\tkfree(reg);\n+\t\t\treturn;\n+\t\t}\n+\t}\n+}\n+\n+static const struct bpf_verifier_ops bm_bpf_verifier_ops = {\n+\t.get_func_proto\t\t= bpf_base_func_proto,\n+\t.is_valid_access\t= bpf_tracing_btf_ctx_access,\n+};\n+\n+static struct bpf_struct_ops bpf_binfmt_misc_ops = {\n+\t.verifier_ops\t= \u0026bm_bpf_verifier_ops,\n+\t.init\t\t= bm_bpf_init,\n+\t.check_member\t= bm_bpf_check_member,\n+\t.init_member\t= bm_bpf_init_member,\n+\t.validate\t= bm_bpf_validate,\n+\t.reg\t\t= bm_bpf_reg,\n+\t.unreg\t\t= bm_bpf_unreg,\n+\t.cfi_stubs\t= \u0026bm_bpf_ops_stubs,\n+\t.name\t\t= \"binfmt_misc_ops\",\n+\t.owner\t\t= THIS_MODULE,\n+};\n+\n+static int __init bm_bpf_struct_ops_init(void)\n+{\n+\treturn register_bpf_struct_ops(\u0026bpf_binfmt_misc_ops, binfmt_misc_ops);\n+}\n+late_initcall(bm_bpf_struct_ops_init);\ndiff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c\nindex 768aca2dc0f034..aa1fe988b6d229 100644\n--- a/fs/bpf_fs_kfuncs.c\n+++ b/fs/bpf_fs_kfuncs.c\n@@ -1,6 +1,7 @@\n // SPDX-License-Identifier: GPL-2.0\n /* Copyright (c) 2024 Google LLC. */\n \n+#include \u003clinux/binfmt_misc.h\u003e\n #include \u003clinux/bpf.h\u003e\n #include \u003clinux/bpf_lsm.h\u003e\n #include \u003clinux/btf.h\u003e\n@@ -387,10 +388,20 @@ BTF_ID_FLAGS(func, bpf_remove_dentry_xattr, KF_SLEEPABLE)\n BTF_ID_FLAGS(func, bpf_real_inode, KF_SLEEPABLE | KF_RET_NULL)\n BTF_KFUNCS_END(bpf_fs_kfunc_set_ids)\n \n+/* Side-effecting kfuncs that stay exclusive to LSM programs. */\n+BTF_SET_START(bpf_fs_kfunc_lsm_only_ids)\n+BTF_ID(func, bpf_set_dentry_xattr)\n+BTF_ID(func, bpf_remove_dentry_xattr)\n+BTF_SET_END(bpf_fs_kfunc_lsm_only_ids)\n+\n static int bpf_fs_kfuncs_filter(const struct bpf_prog *prog, u32 kfunc_id)\n {\n-\tif (!btf_id_set8_contains(\u0026bpf_fs_kfunc_set_ids, kfunc_id) ||\n-\t prog-\u003etype == BPF_PROG_TYPE_LSM)\n+\tif (!btf_id_set8_contains(\u0026bpf_fs_kfunc_set_ids, kfunc_id))\n+\t\treturn 0;\n+\tif (prog-\u003etype == BPF_PROG_TYPE_LSM)\n+\t\treturn 0;\n+\tif (bpf_prog_is_binfmt_misc_ops(prog) \u0026\u0026\n+\t !btf_id_set_contains(\u0026bpf_fs_kfunc_lsm_only_ids, kfunc_id))\n \t\treturn 0;\n \treturn -EACCES;\n }\n@@ -433,7 +444,13 @@ static const struct btf_kfunc_id_set bpf_fs_kfunc_set = {\n \n static int __init bpf_fs_kfuncs_init(void)\n {\n-\treturn register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, \u0026bpf_fs_kfunc_set);\n+\tint ret;\n+\n+\tret = register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, \u0026bpf_fs_kfunc_set);\n+\tif (ret || !IS_ENABLED(CONFIG_BINFMT_MISC_BPF))\n+\t\treturn ret;\n+\treturn register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS,\n+\t\t\t\t\t \u0026bpf_fs_kfunc_set);\n }\n \n late_initcall(bpf_fs_kfuncs_init);\ndiff --git a/fs/exec.c b/fs/exec.c\nindex b92fe7db176cff..7c9e28f549a453 100644\n--- a/fs/exec.c\n+++ b/fs/exec.c\n@@ -1418,6 +1418,7 @@ static void free_bprm(struct linux_binprm *bprm)\n \t/* If a binfmt changed the interp, free it. */\n \tif (bprm-\u003einterp != bprm-\u003efilename)\n \t\tkfree(bprm-\u003einterp);\n+\tkfree(bprm-\u003ebpf_interp);\n \tkfree(bprm-\u003efdpath);\n \tkfree(bprm);\n }\ndiff --git a/include/linux/binfmt_misc.h b/include/linux/binfmt_misc.h\nnew file mode 100644\nindex 00000000000000..e1d26c4301da2c\n--- /dev/null\n+++ b/include/linux/binfmt_misc.h\n@@ -0,0 +1,49 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+#ifndef _LINUX_BINFMT_MISC_H\n+#define _LINUX_BINFMT_MISC_H\n+\n+#include \u003clinux/types.h\u003e\n+\n+struct bpf_prog;\n+struct linux_binprm;\n+struct user_namespace;\n+\n+#define BINFMT_MISC_OPS_NAME_MAX 16\n+\n+/**\n+ * struct binfmt_misc_ops - bpf-backed binary type handler\n+ * @load: match @bprm and select an interpreter via bpf_binprm_set_interp();\n+ * returns \u003e 0 if the binary was handled, 0 to fall through to the\n+ * handlers registered after this one, a negative errno to fail the\n+ * exec; -ENOEXEC does not fail the exec but moves on to the\n+ * remaining binary formats\n+ * @name: name that 'B' entries reference the handler by\n+ */\n+struct binfmt_misc_ops {\n+\tint (*load)(struct linux_binprm *bprm);\n+\tchar name[BINFMT_MISC_OPS_NAME_MAX];\n+};\n+\n+#ifdef CONFIG_BINFMT_MISC_BPF\n+const struct binfmt_misc_ops *binfmt_misc_get_ops(struct user_namespace *user_ns,\n+\t\t\t\t\t\t const char *name);\n+void binfmt_misc_put_ops(const struct binfmt_misc_ops *ops);\n+bool bpf_prog_is_binfmt_misc_ops(const struct bpf_prog *prog);\n+#else\n+static inline const struct binfmt_misc_ops *\n+binfmt_misc_get_ops(struct user_namespace *user_ns, const char *name)\n+{\n+\treturn NULL;\n+}\n+\n+static inline void binfmt_misc_put_ops(const struct binfmt_misc_ops *ops)\n+{\n+}\n+\n+static inline bool bpf_prog_is_binfmt_misc_ops(const struct bpf_prog *prog)\n+{\n+\treturn false;\n+}\n+#endif /* CONFIG_BINFMT_MISC_BPF */\n+\n+#endif /* _LINUX_BINFMT_MISC_H */\ndiff --git a/include/linux/binfmts.h b/include/linux/binfmts.h\nindex 2c77e383e73757..0070d207d54f04 100644\n--- a/include/linux/binfmts.h\n+++ b/include/linux/binfmts.h\n@@ -65,6 +65,7 @@ struct linux_binprm {\n \t\t\t\t of the time same as filename, but could be\n \t\t\t\t different for binfmt_{misc,script} */\n \tconst char *fdpath;\t/* generated filename for execveat */\n+\tconst char *bpf_interp;\t/* interpreter selected by a bpf handler */\n \tunsigned interp_flags;\n \tint execfd;\t\t/* File descriptor of the executable */\n \tunsigned long exec;\n"
} |
| 2/1 | 2026/07/08 06:00 | agent | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: {
"EnableConfigs": [
"BINFMT_MISC_BPF"
],
"FocusSymbols": [
"bpf_binprm_set_interp",
"bm_bpf_reg",
"bm_bpf_unreg",
"create_entry",
"bm_register_write"
],
"Reasoning": "The patch introduces a new feature allowing BPF programs to act as handlers for binfmt_misc. It adds new BPF struct_ops and kfuncs, and modifies the binfmt_misc registration and execution logic. Fuzzing should focus on the BPF registration/unregistration, the new kfunc, and the binfmt_misc registration path.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Do not list too generic configs (we already have them enabled). Only list those that specifically cover the modified code. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit a6912d09464c93f365054dd673c2ad197f3054f3
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Jul 8 06:00:26 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/admin-guide/binfmt-misc.rst b/Documentation/admin-guide/binfmt-misc.rst
index c0a34fbf8022b5..c0efb077362843 100644
--- a/Documentation/admin-guide/binfmt-misc.rst
+++ b/Documentation/admin-guide/binfmt-misc.rst
@@ -26,11 +26,13 @@ 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...``).
- Ignored when using filename extension matching.
+ Ignored when using filename extension matching. For ``B`` entries this
+ field carries the name of the bpf handler instead.
- ``magic``
is the byte sequence binfmt_misc is matching for. The magic string
may contain hex-encoded characters like ``\x0a`` or ``\xA4``. Note that you
@@ -97,6 +99,40 @@ There are some restrictions:
offset+size(magic) has to be less than 128
- the interpreter string may not exceed 127 characters
+
+bpf-backed handlers
+-------------------
+
+With ``CONFIG_BINFMT_MISC_BPF`` both the matching and the interpreter
+selection can be delegated to a bpf program. A handler is an instance of the
+``binfmt_misc_ops`` struct_ops with a sleepable ``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 and carries neither magic,
+mask, nor interpreter::
+
+ echo ':qemu:B:my_handler::::' > register
+
+At exec time the ``load`` program receives the ``linux_binprm`` of the
+binary. It can match on the header in ``bprm->buf``, read the file itself,
+e.g. to parse ELF program headers, 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 a
+positive value. Returning ``0`` falls through to the handlers registered
+after this one, a negative errno fails the exec with that error;
+``-ENOEXEC`` ends the binfmt_misc search but lets the remaining binary
+formats have a go. The interpreter is opened with the credentials of the
+task doing the exec, exactly as a statically registered interpreter would
+be.
+
+Handlers are looked up in the user namespace the struct_ops map was
+registered in, falling back to ancestor namespaces, mirroring how
+binfmt_misc instances themselves are looked up. The entry keeps the handler
+alive; deleting the struct_ops map only prevents new registrations.
+
+The ``C`` and ``F`` flags cannot be combined with ``B`` entries: there is no
+fixed interpreter to pre-open and a program-selected interpreter must never
+inherit the credentials of a setuid binary.
+
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
a line ``none /proc/sys/fs/binfmt_misc binfmt_misc defaults 0 0`` to your
diff --git a/fs/Kconfig.binfmt b/fs/Kconfig.binfmt
index 1949e25c7741b1..daeac4889d03ef 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 89a8a9d207d1c5..499c6670f0c183 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_misc.c b/fs/binfmt_misc.c
index 84349fcb93f13a..b80e219ba5876b 100644
--- a/fs/binfmt_misc.c
+++ b/fs/binfmt_misc.c
@@ -17,6 +17,7 @@
#include <linux/sched/mm.h>
#include <linux/magic.h>
#include <linux/binfmts.h>
+#include <linux/binfmt_misc.h>
#include <linux/slab.h>
#include <linux/ctype.h>
#include <linux/string_helpers.h>
@@ -41,7 +42,7 @@ enum {
VERBOSE_STATUS = 1 /* make it zero to save 400 bytes kernel memory */
};
-enum {Enabled, Magic};
+enum {Enabled, Magic, Bpf};
#define MISC_FMT_PRESERVE_ARGV0 (1UL << 31)
#define MISC_FMT_OPEN_BINARY (1UL << 30)
#define MISC_FMT_CREDENTIALS (1UL << 29)
@@ -58,6 +59,8 @@ typedef struct {
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;
refcount_t users; /* sync removal with load_misc_binary() */
} Node;
@@ -82,14 +85,19 @@ static struct file_system_type bm_fs_type;
* 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
+ * @bpf_skip: number of bpf-backed handlers to skip over
*
* Search for a binary type handler for @bprm in the list of registered binary
- * type handlers.
+ * type handlers. A bpf-backed handler cannot be matched here as its program
+ * must run in sleepable context; it is returned as a candidate and the
+ * program decides in load_misc_binary(). @bpf_skip allows the caller to
+ * resume the search after the first @bpf_skip candidates declined.
*
* Return: binary type list entry on success, NULL on failure
*/
static Node *search_binfmt_handler(struct binfmt_misc *misc,
- struct linux_binprm *bprm)
+ struct linux_binprm *bprm,
+ unsigned int bpf_skip)
{
char *p = strrchr(bprm->interp, '.');
Node *e;
@@ -103,6 +111,15 @@ static Node *search_binfmt_handler(struct binfmt_misc *misc,
if (!test_bit(Enabled, &e->flags))
continue;
+ /* The program decides in load_misc_binary(). */
+ if (test_bit(Bpf, &e->flags)) {
+ if (bpf_skip) {
+ bpf_skip--;
+ continue;
+ }
+ return e;
+ }
+
/* Do matching based on extension if applicable. */
if (!test_bit(Magic, &e->flags)) {
if (p && !strcmp(e->magic, p + 1))
@@ -132,6 +149,7 @@ static Node *search_binfmt_handler(struct binfmt_misc *misc,
* get_binfmt_handler - try to find a binary type handler
* @misc: handle to binfmt_misc instance
* @bprm: binary for which we are looking for a handler
+ * @bpf_skip: number of bpf-backed handlers to skip over
*
* 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().
@@ -139,12 +157,13 @@ static Node *search_binfmt_handler(struct binfmt_misc *misc,
* Return: binary type list entry on success, NULL on failure
*/
static Node *get_binfmt_handler(struct binfmt_misc *misc,
- struct linux_binprm *bprm)
+ struct linux_binprm *bprm,
+ unsigned int bpf_skip)
{
Node *e;
read_lock(&misc->entries_lock);
- e = search_binfmt_handler(misc, bprm);
+ e = search_binfmt_handler(misc, bprm, bpf_skip);
if (e)
refcount_inc(&e->users);
read_unlock(&misc->entries_lock);
@@ -164,6 +183,8 @@ static void put_binfmt_handler(Node *e)
if (refcount_dec_and_test(&e->users)) {
if (e->flags & MISC_FMT_OPEN_FILE)
filp_close(e->interp_file, NULL);
+ if (e->bpf_ops)
+ binfmt_misc_put_ops(e->bpf_ops);
kfree(e);
}
}
@@ -206,12 +227,15 @@ static int load_misc_binary(struct linux_binprm *bprm)
struct file *interp_file = NULL;
int retval = -ENOEXEC;
struct binfmt_misc *misc;
+ const char *interpreter;
+ unsigned int bpf_skip = 0;
misc = load_binfmt_misc();
if (!misc->enabled)
return retval;
- fmt = get_binfmt_handler(misc, bprm);
+retry:
+ fmt = get_binfmt_handler(misc, bprm, bpf_skip);
if (!fmt)
return retval;
@@ -220,6 +244,32 @@ static int load_misc_binary(struct linux_binprm *bprm)
if (bprm->interp_flags & BINPRM_FLAGS_PATH_INACCESSIBLE)
goto ret;
+ if (test_bit(Bpf, &fmt->flags)) {
+ retval = fmt->bpf_ops->load(bprm);
+ if (retval < 0) {
+ /* Keep a program-supplied error within errno range. */
+ if (retval < -MAX_ERRNO)
+ retval = -ENOEXEC;
+ goto ret;
+ }
+ if (!retval) {
+ /* Declined, move on to later handlers. */
+ kfree(bprm->bpf_interp);
+ bprm->bpf_interp = NULL;
+ put_binfmt_handler(fmt);
+ bpf_skip++;
+ retval = -ENOEXEC;
+ goto retry;
+ }
+ /* Selecting an interpreter is part of the contract. */
+ retval = -ENOEXEC;
+ if (!bprm->bpf_interp)
+ goto ret;
+ interpreter = bprm->bpf_interp;
+ } else {
+ interpreter = fmt->interpreter;
+ }
+
if (fmt->flags & MISC_FMT_PRESERVE_ARGV0) {
bprm->interp_flags |= BINPRM_FLAGS_PRESERVE_ARGV0;
} else {
@@ -238,13 +288,13 @@ static int load_misc_binary(struct linux_binprm *bprm)
bprm->argc++;
/* add the interp as argv[0] */
- retval = copy_string_kernel(fmt->interpreter, bprm);
+ retval = copy_string_kernel(interpreter, bprm);
if (retval < 0)
goto ret;
bprm->argc++;
/* Update interp in case binfmt_script needs it. */
- retval = bprm_change_interp(fmt->interpreter, bprm);
+ retval = bprm_change_interp(interpreter, bprm);
if (retval < 0)
goto ret;
@@ -253,7 +303,7 @@ static int load_misc_binary(struct linux_binprm *bprm)
if (!IS_ERR(interp_file))
deny_write_access(interp_file);
} else {
- interp_file = open_exec(fmt->interpreter);
+ interp_file = open_exec(interpreter);
}
retval = PTR_ERR(interp_file);
if (IS_ERR(interp_file))
@@ -265,6 +315,9 @@ static int load_misc_binary(struct linux_binprm *bprm)
retval = 0;
ret:
+ /* A program-selected interpreter is consumed by this exec attempt. */
+ kfree(bprm->bpf_interp);
+ bprm->bpf_interp = NULL;
/*
* If we actually put the node here all concurrent calls to
@@ -404,13 +457,47 @@ static Node *create_entry(const char __user *buffer, size_t count)
pr_debug("register: type: M (magic)\n");
e->flags = (1 << Enabled) | (1 << Magic);
break;
+ case 'B':
+ if (!IS_ENABLED(CONFIG_BINFMT_MISC_BPF))
+ goto einval;
+ pr_debug("register: type: B (bpf)\n");
+ e->flags = (1 << Enabled) | (1 << Bpf);
+ break;
default:
goto einval;
}
if (*p++ != del)
goto einval;
- if (test_bit(Magic, &e->flags)) {
+ if (test_bit(Bpf, &e->flags)) {
+ char *s;
+
+ /* The 'offset' field carries the handler name. */
+ s = strchr(p, del);
+ if (!s)
+ goto einval;
+ *s++ = '\0';
+ e->bpf_ops_name = p;
+ if (!e->bpf_ops_name[0] ||
+ strlen(e->bpf_ops_name) >= BINFMT_MISC_OPS_NAME_MAX)
+ goto einval;
+ p = s;
+ pr_debug("register: bpf handler: {%s}\n", e->bpf_ops_name);
+
+ /* The 'magic' field must be empty. */
+ s = strchr(p, del);
+ if (!s || s != p)
+ goto einval;
+ *s++ = '\0';
+ p = s;
+
+ /* The 'mask' field must be empty. */
+ s = strchr(p, del);
+ if (!s || s != p)
+ goto einval;
+ *s++ = '\0';
+ p = s;
+ } else if (test_bit(Magic, &e->flags)) {
/* Handle the 'M' (magic) format. */
char *s;
@@ -524,8 +611,13 @@ static Node *create_entry(const char __user *buffer, size_t count)
if (!p)
goto einval;
*p++ = '\0';
- if (!e->interpreter[0])
+ if (test_bit(Bpf, &e->flags)) {
+ /* The program selects the interpreter at exec time. */
+ if (e->interpreter[0])
+ goto einval;
+ } else if (!e->interpreter[0]) {
goto einval;
+ }
pr_debug("register: interpreter: {%s}\n", e->interpreter);
/* Parse the 'flags' field. */
@@ -535,6 +627,14 @@ static Node *create_entry(const char __user *buffer, size_t count)
if (p != buf + count)
goto einval;
+ /*
+ * A program-selected interpreter cannot be pre-opened and must not
+ * inherit the credentials of a setuid binary it was chosen for.
+ */
+ if (test_bit(Bpf, &e->flags) &&
+ (e->flags & (MISC_FMT_CREDENTIALS | MISC_FMT_OPEN_FILE)))
+ goto einval;
+
return e;
out:
@@ -588,7 +688,10 @@ static void entry_status(Node *e, char *page)
return;
}
- dp += sprintf(dp, "%s\ninterpreter %s\n", status, e->interpreter);
+ if (test_bit(Bpf, &e->flags))
+ dp += sprintf(dp, "%s\nbpf %s\n", status, e->bpf_ops_name);
+ else
+ dp += sprintf(dp, "%s\ninterpreter %s\n", status, e->interpreter);
/* print the special flags */
dp += sprintf(dp, "flags: ");
@@ -602,7 +705,9 @@ static void entry_status(Node *e, char *page)
*dp++ = 'F';
*dp++ = '\n';
- if (!test_bit(Magic, &e->flags)) {
+ if (test_bit(Bpf, &e->flags)) {
+ *dp = '\0';
+ } else if (!test_bit(Magic, &e->flags)) {
sprintf(dp, "extension .%s\n", e->magic);
} else {
dp += sprintf(dp, "offset %i\nmagic ", e->offset);
@@ -809,6 +914,16 @@ static ssize_t bm_register_write(struct file *file, const char __user *buffer,
if (IS_ERR(e))
return PTR_ERR(e);
+ if (test_bit(Bpf, &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);
+ kfree(e);
+ return -ENOENT;
+ }
+ }
+
if (e->flags & MISC_FMT_OPEN_FILE) {
/*
* Now that we support unprivileged binfmt_misc mounts make
@@ -834,6 +949,8 @@ static ssize_t bm_register_write(struct file *file, const char __user *buffer,
exe_file_allow_write_access(f);
filp_close(f, NULL);
}
+ if (e->bpf_ops)
+ binfmt_misc_put_ops(e->bpf_ops);
kfree(e);
return err;
}
diff --git a/fs/binfmt_misc_bpf.c b/fs/binfmt_misc_bpf.c
new file mode 100644
index 00000000000000..72da0964d94915
--- /dev/null
+++ b/fs/binfmt_misc_bpf.c
@@ -0,0 +1,275 @@
+// 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
+ */
+
+#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/init.h>
+#include <linux/limits.h>
+#include <linux/mutex.h>
+#include <linux/slab.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_MUTEX(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
+ *
+ * Search @user_ns and its ancestors for a handler named @name, mirroring
+ * the instance lookup in load_binfmt_misc(). 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)
+{
+ const struct user_namespace *ns;
+ struct bm_bpf_ops_reg *reg;
+
+ guard(mutex)(&bm_bpf_ops_lock);
+
+ for (ns = user_ns; ns; ns = ns->parent) {
+ reg = bm_bpf_ops_find(ns, name);
+ if (!reg)
+ continue;
+ if (!bpf_struct_ops_get(reg->ops))
+ return NULL;
+ return reg->ops;
+ }
+ return NULL;
+}
+
+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;
+}
+
+__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 a positive value. The path is opened with the
+ * credentials of the task doing the exec after the program returns.
+ *
+ * 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;
+
+ kfree(bprm->bpf_interp);
+ bprm->bpf_interp = interp;
+ return 0;
+}
+
+__bpf_kfunc_end_defs();
+
+BTF_KFUNCS_START(bm_bpf_kfunc_ids)
+BTF_ID_FLAGS(func, bpf_binprm_set_interp, 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 (bpf_prog_is_binfmt_misc_ops(prog))
+ 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 int bm_bpf_ops__load(struct linux_binprm *bprm)
+{
+ return 0;
+}
+
+static struct binfmt_misc_ops bm_bpf_ops_stubs = {
+ .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, 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->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(mutex)(&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(®->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(mutex)(&bm_bpf_ops_lock);
+
+ list_for_each_entry(reg, &bm_bpf_ops_list, list) {
+ if (reg->ops == kdata && reg->link == link) {
+ list_del(®->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 768aca2dc0f034..aa1fe988b6d229 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>
@@ -387,10 +388,20 @@ BTF_ID_FLAGS(func, bpf_remove_dentry_xattr, KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_real_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 (bpf_prog_is_binfmt_misc_ops(prog) &&
+ !btf_id_set_contains(&bpf_fs_kfunc_lsm_only_ids, kfunc_id))
return 0;
return -EACCES;
}
@@ -433,7 +444,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 b92fe7db176cff..7c9e28f549a453 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1418,6 +1418,7 @@ static void free_bprm(struct linux_binprm *bprm)
/* If a binfmt changed the interp, free it. */
if (bprm->interp != bprm->filename)
kfree(bprm->interp);
+ kfree(bprm->bpf_interp);
kfree(bprm->fdpath);
kfree(bprm);
}
diff --git a/include/linux/binfmt_misc.h b/include/linux/binfmt_misc.h
new file mode 100644
index 00000000000000..e1d26c4301da2c
--- /dev/null
+++ b/include/linux/binfmt_misc.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_BINFMT_MISC_H
+#define _LINUX_BINFMT_MISC_H
+
+#include <linux/types.h>
+
+struct bpf_prog;
+struct linux_binprm;
+struct user_namespace;
+
+#define BINFMT_MISC_OPS_NAME_MAX 16
+
+/**
+ * struct binfmt_misc_ops - bpf-backed binary type handler
+ * @load: match @bprm and select an interpreter via bpf_binprm_set_interp();
+ * returns > 0 if the binary was handled, 0 to fall through to the
+ * handlers registered after this one, a negative errno to fail the
+ * exec; -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 {
+ 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 2c77e383e73757..0070d207d54f04 100644
--- a/include/linux/binfmts.h
+++ b/include/linux/binfmts.h
@@ -65,6 +65,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 */
+ const char *bpf_interp; /* interpreter selected by a bpf handler */
unsigned interp_flags;
int execfd; /* File descriptor of the executable */
unsigned long exec;
|
| 3/2 | 2026/07/08 06:00 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 12090 output: 163 thoughts: 1312 Thoughts: **Analysis of a BPF-Backed `binfmt_misc` Patch for Fuzzing** Okay, so I'm looking at this patch and it's got my attention. It introduces a significant functional change: BPF programs handling binary formats via `binfmt_misc`. The core modifications are in `fs/binfmt_misc.c`, a new `fs/binfmt_misc_bpf.c`, and changes to `fs/exec.c` and `fs/bpf_fs_kfuncs.c`. This is definitely something worth fuzzing, given the added complexity and potential for new vulnerabilities. The key is this new config, `BINFMT_MISC_BPF`. I quickly checked `fs/Kconfig.binfmt` to make sure I'm right about the config name (yup, `BINFMT_MISC_BPF`), and verified its dependencies: it needs `BINFMT_MISC=y`, `BPF_SYSCALL`, `BPF_JIT`, and `DEBUG_INFO_BTF`. Now, I need to identify the crucial functions and areas to target. Looking at the code changes, these are the functions that immediately stand out in the modified files: In `fs/binfmt_misc.c`: `search_binfmt_handler`, `get_binfmt_handler`, `load_misc_binary`, `create_entry`, `entry_status`, `bm_register_write`. In `fs/binfmt_misc_bpf.c`: `binfmt_misc_get_ops`, `binfmt_misc_put_ops`, `bpf_prog_is_binfmt_misc_ops`, `bpf_binprm_set_interp`, `bm_bpf_kfunc_filter`, `bm_bpf_ops__load`, `bm_bpf_init`, `bm_bpf_check_member`, `bm_bpf_init_member`, `bm_bpf_validate`, `bm_bpf_reg`, `bm_bpf_unreg`, `bm_bpf_struct_ops_init`. In `fs/bpf_fs_kfuncs.c`: `bpf_fs_kfuncs_filter`, `bpf_fs_kfuncs_init`. And in `fs/exec.c`: `free_bprm`. So, *definitely* fuzzable. The config to enable the new functionality is `BINFMT_MISC_BPF`. `BINFMT_MISC` is also a dependency, but that's typically enabled by default, so I'll just list `BINFMT_MISC_BPF` for now. Now, which functions are the *best* to focus on during fuzzing? `load_misc_binary` looks tempting, as it's at the core of the exec path, but it might be too broad a target if it's called on every `execve` if `binfmt_misc` is enabled. It has new BPF logic though, so it might be good. `create_entry` and `bm_register_write` are triggered when writing to `/proc/sys/fs/binfmt_misc/register`, which is a good targeted approach, and new for this feature. `bpf_binprm_set_interp` is a BPF kfunc specifically introduced by this patch, so it's critical. `bm_bpf_reg` and `bm_bpf_unreg` are also key, as they are involved in registering and unregistering the BPF `struct_ops`. Let's make sure the configs and focus symbols are correct: Config: `BINFMT_MISC_BPF` Focus Symbols: `bpf_binprm_set_interp`, `bm_bpf_reg`, `bm_bpf_unreg`, `create_entry`, `bm_register_write` I'll omit `load_misc_binary` to avoid testing execve in general and to focus tests on new code, but `create_entry` and `bm_register_write` look very good. Okay, time to use the `set-results` tool. `WorthFuzzing: true` `EnableConfigs: ["BINFMT_MISC_BPF"]` `FocusSymbols: ["bpf_binprm_set_interp", "bm_bpf_reg", "bm_bpf_unreg", "create_entry", "bm_register_write"]` `Reasoning: "The patch introduces a new feature allowing BPF programs to act as handlers for binfmt_misc. It |
| 4/2 | 2026/07/08 06:00 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"BINFMT_MISC_BPF"
],
"FocusSymbols": [
"bpf_binprm_set_interp",
"bm_bpf_reg",
"bm_bpf_unreg",
"create_entry",
"bm_register_write"
],
"Reasoning": "The patch introduces a new feature allowing BPF programs to act as handlers for binfmt_misc. It adds new BPF struct_ops and kfuncs, and modifies the binfmt_misc registration and execution logic. Fuzzing should focus on the BPF registration/unregistration, the new kfunc, and the binfmt_misc registration path.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"BINFMT_MISC_BPF"
],
"FocusSymbols": [
"bpf_binprm_set_interp",
"bm_bpf_reg",
"bm_bpf_unreg",
"create_entry",
"bm_register_write"
],
"Reasoning": "The patch introduces a new feature allowing BPF programs to act as handlers for binfmt_misc. It adds new BPF struct_ops and kfuncs, and modifies the binfmt_misc registration and execution logic. Fuzzing should focus on the BPF registration/unregistration, the new kfunc, and the binfmt_misc registration path.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|