AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/10 20:32 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": [
    "BINFMT_MISC"
  ],
  "FocusSymbols": [
    "load_misc_binary",
    "bm_register_write",
    "bm_entry_write",
    "bm_status_write",
    "bm_unlink",
    "bm_entry_show"
  ],
  "Reasoning": "The patch significantly refactors the binfmt_misc filesystem, introducing RCU-based lookups, changing internal locking from rwlock to spinlock, converting entry reading to use seq_file, and adding support for removing entries via unlink(2). These are functional changes to reachable core kernel logic that should be fuzzed to ensure no regressions, memory leaks, or concurrency issues were introduced.",
  "WorthFuzzing": true
}

1/1 2026/07/10 20:32 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit a877db1bfb7d2d8a5bcfea03f3fc112c8403ad32\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Jul 10 20:32:37 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..306ef48f5de6dd 100644\n--- a/Documentation/admin-guide/binfmt-misc.rst\n+++ b/Documentation/admin-guide/binfmt-misc.rst\n@@ -133,7 +133,8 @@ or 1 (to enable) to ``/proc/sys/fs/binfmt_misc/status`` or\n Catting the file tells you the current status of ``binfmt_misc/the_entry``.\n \n You can remove one entry or all entries by echoing -1 to ``/proc/.../the_name``\n-or ``/proc/sys/fs/binfmt_misc/status``.\n+or ``/proc/sys/fs/binfmt_misc/status``. A single entry can also be removed\n+by simply unlinking (``rm``) ``/proc/.../the_name``.\n \n \n Hints\ndiff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c\nindex 84349fcb93f13a..f51d2726986160 100644\n--- a/fs/binfmt_misc.c\n+++ b/fs/binfmt_misc.c\n@@ -10,45 +10,46 @@\n \n #define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n \n-#include \u003clinux/kernel.h\u003e\n-#include \u003clinux/module.h\u003e\n-#include \u003clinux/hex.h\u003e\n-#include \u003clinux/init.h\u003e\n-#include \u003clinux/sched/mm.h\u003e\n-#include \u003clinux/magic.h\u003e\n #include \u003clinux/binfmts.h\u003e\n-#include \u003clinux/slab.h\u003e\n+#include \u003clinux/bitops.h\u003e\n+#include \u003clinux/bits.h\u003e\n+#include \u003clinux/bug.h\u003e\n+#include \u003clinux/cleanup.h\u003e\n+#include \u003clinux/cred.h\u003e\n #include \u003clinux/ctype.h\u003e\n-#include \u003clinux/string_helpers.h\u003e\n #include \u003clinux/file.h\u003e\n-#include \u003clinux/pagemap.h\u003e\n-#include \u003clinux/namei.h\u003e\n-#include \u003clinux/mount.h\u003e\n-#include \u003clinux/fs_context.h\u003e\n-#include \u003clinux/syscalls.h\u003e\n #include \u003clinux/fs.h\u003e\n+#include \u003clinux/fs_context.h\u003e\n+#include \u003clinux/init.h\u003e\n+#include \u003clinux/kstrtox.h\u003e\n+#include \u003clinux/magic.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/printk.h\u003e\n+#include \u003clinux/rculist.h\u003e\n+#include \u003clinux/refcount.h\u003e\n+#include \u003clinux/seq_file.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/string.h\u003e\n+#include \u003clinux/string_helpers.h\u003e\n #include \u003clinux/uaccess.h\u003e\n+#include \u003clinux/user_namespace.h\u003e\n \n-#include \"internal.h\"\n-\n-#ifdef DEBUG\n-# define USE_DEBUG 1\n-#else\n-# define USE_DEBUG 0\n-#endif\n-\n-enum {\n-\tVERBOSE_STATUS = 1 /* make it zero to save 400 bytes kernel memory */\n+/* Entry status and match type bit numbers. */\n+enum binfmt_misc_entry_bits {\n+\tMISC_FMT_ENABLED_BIT\t= 0,\n+\tMISC_FMT_MAGIC_BIT\t= 1,\n };\n \n-enum {Enabled, Magic};\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-#define MISC_FMT_OPEN_FILE (1UL \u003c\u003c 28)\n+/* Entry behavior flags, fixed at registration time. */\n+enum binfmt_misc_entry_flags {\n+\tMISC_FMT_PRESERVE_ARGV0\t= (1U \u003c\u003c 31),\n+\tMISC_FMT_OPEN_BINARY\t= (1U \u003c\u003c 30),\n+\tMISC_FMT_CREDENTIALS\t= (1U \u003c\u003c 29),\n+\tMISC_FMT_OPEN_FILE\t= (1U \u003c\u003c 28),\n+};\n \n-typedef struct {\n-\tstruct list_head list;\n+struct binfmt_misc_entry {\n+\tstruct hlist_node node;\n \tunsigned long flags;\t\t/* type, status, etc. */\n \tint offset;\t\t\t/* offset of magic */\n \tint size;\t\t\t/* size of magic/mask */\n@@ -59,9 +60,9 @@ typedef struct {\n \tstruct dentry *dentry;\n \tstruct file *interp_file;\n \trefcount_t users;\t\t/* sync removal with load_misc_binary() */\n-} Node;\n-\n-static struct file_system_type bm_fs_type;\n+\tstruct rcu_head rcu;\n+\tchar buf[];\t\t\t/* register string, fields point in here */\n+};\n \n /*\n  * Max length of the register string.  Determined by:\n@@ -74,10 +75,36 @@ static struct file_system_type bm_fs_type;\n  *  - interp: ~50 bytes\n  *  - flags:  5 bytes\n  * Round that up a bit, and then back off to hold the internal data\n- * (like struct Node).\n+ * (like struct binfmt_misc_entry).\n  */\n #define MAX_REGISTER_LENGTH 1920\n \n+/* Trailing delimiter pad so field parsing always terminates at a delimiter. */\n+#define MISC_DELIM_PAD 8\n+\n+/* Check if @e's magic matches @bprm's buffer, applying the mask if set. */\n+static bool entry_matches_magic(const struct binfmt_misc_entry *e,\n+\t\t\t\tconst struct linux_binprm *bprm)\n+{\n+\tconst char *s = bprm-\u003ebuf + e-\u003eoffset;\n+\tint i;\n+\n+\tif (!e-\u003emask)\n+\t\treturn !memcmp(s, e-\u003emagic, e-\u003esize);\n+\n+\tfor (i = 0; i \u003c e-\u003esize; i++)\n+\t\tif ((s[i] ^ e-\u003emagic[i]) \u0026 e-\u003emask[i])\n+\t\t\treturn false;\n+\treturn true;\n+}\n+\n+/* Check if @e's registered extension matches @ext, NULL if there is none. */\n+static bool entry_matches_extension(const struct binfmt_misc_entry *e,\n+\t\t\t\t    const char *ext)\n+{\n+\treturn ext \u0026\u0026 !strcmp(e-\u003emagic, ext);\n+}\n+\n /**\n  * search_binfmt_handler - search for a binary handler for @bprm\n  * @misc: handle to binfmt_misc instance\n@@ -86,43 +113,30 @@ static struct file_system_type bm_fs_type;\n  * Search for a binary type handler for @bprm in the list of registered binary\n  * type handlers.\n  *\n+ * The caller must hold the RCU read lock.\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+static struct binfmt_misc_entry *\n+search_binfmt_handler(struct binfmt_misc *misc, struct linux_binprm *bprm)\n {\n-\tchar *p = strrchr(bprm-\u003einterp, '.');\n-\tNode *e;\n+\tchar *dot = strrchr(bprm-\u003einterp, '.');\n+\tconst char *ext = dot ? dot + 1 : NULL;\n+\tstruct binfmt_misc_entry *e;\n \n \t/* Walk all the registered handlers. */\n-\tlist_for_each_entry(e, \u0026misc-\u003eentries, list) {\n-\t\tchar *s;\n-\t\tint j;\n-\n+\thlist_for_each_entry_rcu(e, \u0026misc-\u003eentries, node) {\n \t\t/* Make sure this one is currently enabled. */\n-\t\tif (!test_bit(Enabled, \u0026e-\u003eflags))\n+\t\tif (!test_bit(MISC_FMT_ENABLED_BIT, \u0026e-\u003eflags))\n \t\t\tcontinue;\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+\t\tif (test_bit(MISC_FMT_MAGIC_BIT, \u0026e-\u003eflags)) {\n+\t\t\tif (entry_matches_magic(e, bprm))\n \t\t\t\treturn e;\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\t/* Do matching based on magic \u0026 mask. */\n-\t\ts = bprm-\u003ebuf + e-\u003eoffset;\n-\t\tif (e-\u003emask) {\n-\t\t\tfor (j = 0; j \u003c e-\u003esize; j++)\n-\t\t\t\tif ((*s++ ^ e-\u003emagic[j]) \u0026 e-\u003emask[j])\n-\t\t\t\t\tbreak;\n \t\t} else {\n-\t\t\tfor (j = 0; j \u003c e-\u003esize; j++)\n-\t\t\t\tif ((*s++ ^ e-\u003emagic[j]))\n-\t\t\t\t\tbreak;\n+\t\t\tif (entry_matches_extension(e, ext))\n+\t\t\t\treturn e;\n \t\t}\n-\t\tif (j == e-\u003esize)\n-\t\t\treturn e;\n \t}\n \n \treturn NULL;\n@@ -134,64 +148,69 @@ static Node *search_binfmt_handler(struct binfmt_misc *misc,\n  * @bprm: binary for which we are looking for a handler\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+ * reference to protect against removal via bm_{entry,status}_write(). The\n+ * refcount of an entry can only drop to zero once it has been unlinked and\n+ * a restarted search cannot find an unlinked entry again so the retry loop\n+ * is bounded.\n  *\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+static struct binfmt_misc_entry *get_binfmt_handler(struct binfmt_misc *misc,\n+\t\t\t\t\t\t    struct linux_binprm *bprm)\n {\n-\tNode *e;\n+\tstruct binfmt_misc_entry *e;\n \n-\tread_lock(\u0026misc-\u003eentries_lock);\n-\te = search_binfmt_handler(misc, bprm);\n-\tif (e)\n-\t\trefcount_inc(\u0026e-\u003eusers);\n-\tread_unlock(\u0026misc-\u003eentries_lock);\n+\tguard(rcu)();\n+\tdo {\n+\t\te = search_binfmt_handler(misc, bprm);\n+\t} while (e \u0026\u0026 !refcount_inc_not_zero(\u0026e-\u003eusers));\n \treturn e;\n }\n \n /**\n- * put_binfmt_handler - put binary handler node\n- * @e: node to put\n+ * put_binfmt_handler - put binary handler entry\n+ * @e: entry to put\n  *\n- * Free node syncing with load_misc_binary() and defer final free to\n+ * Free entry syncing with load_misc_binary() and defer final free to\n  * load_misc_binary() in case it is using the binary type handler we were\n  * requested to remove.\n  */\n-static void put_binfmt_handler(Node *e)\n+static void put_binfmt_handler(struct binfmt_misc_entry *e)\n {\n \tif (refcount_dec_and_test(\u0026e-\u003eusers)) {\n-\t\tif (e-\u003eflags \u0026 MISC_FMT_OPEN_FILE)\n+\t\tif (e-\u003eflags \u0026 MISC_FMT_OPEN_FILE) {\n+\t\t\texe_file_allow_write_access(e-\u003einterp_file);\n \t\t\tfilp_close(e-\u003einterp_file, NULL);\n-\t\tkfree(e);\n+\t\t}\n+\t\t/* Lockless walkers may still dereference this entry. */\n+\t\tkfree_rcu(e, rcu);\n \t}\n }\n \n+DEFINE_FREE(put_binfmt_handler, struct binfmt_misc_entry *, if (_T) put_binfmt_handler(_T))\n+\n /**\n- * load_binfmt_misc - load the binfmt_misc of the caller's user namespace\n+ * current_binfmt_misc - get the binfmt_misc instance of the caller's user namespace\n  *\n- * To be called in load_misc_binary() to load the relevant struct binfmt_misc.\n- * If a user namespace doesn't have its own binfmt_misc mount it can make use\n- * of its ancestor's binfmt_misc handlers. This mimicks the behavior of\n- * pre-namespaced binfmt_misc where all registered binfmt_misc handlers where\n- * available to all user and user namespaces on the system.\n+ * If a user namespace doesn't have its own binfmt_misc mount it uses the\n+ * handlers of its closest ancestor with one. This mimics the behavior of\n+ * pre-namespaced binfmt_misc where all registered handlers were available\n+ * to all users and user namespaces on the system. The init user namespace\n+ * instance is statically set up so the fallback is never reached in\n+ * practice.\n  *\n  * Return: the binfmt_misc instance of the caller's user namespace\n  */\n-static struct binfmt_misc *load_binfmt_misc(void)\n+static struct binfmt_misc *current_binfmt_misc(void)\n {\n \tconst struct user_namespace *user_ns;\n \tstruct binfmt_misc *misc;\n \n-\tuser_ns = current_user_ns();\n-\twhile (user_ns) {\n+\tfor (user_ns = current_user_ns(); user_ns; user_ns = user_ns-\u003eparent) {\n \t\t/* Pairs with smp_store_release() in bm_fill_super(). */\n \t\tmisc = smp_load_acquire(\u0026user_ns-\u003ebinfmt_misc);\n \t\tif (misc)\n \t\t\treturn misc;\n-\n-\t\tuser_ns = user_ns-\u003eparent;\n \t}\n \n \treturn \u0026init_binfmt_misc;\n@@ -202,30 +221,29 @@ static struct binfmt_misc *load_binfmt_misc(void)\n  */\n static int load_misc_binary(struct linux_binprm *bprm)\n {\n-\tNode *fmt;\n-\tstruct file *interp_file = NULL;\n-\tint retval = -ENOEXEC;\n+\tstruct binfmt_misc_entry *fmt __free(put_binfmt_handler) = NULL;\n+\tstruct file *interp_file;\n \tstruct binfmt_misc *misc;\n+\tint retval;\n \n-\tmisc = load_binfmt_misc();\n-\tif (!misc-\u003eenabled)\n-\t\treturn retval;\n+\tmisc = current_binfmt_misc();\n+\tif (!READ_ONCE(misc-\u003eenabled))\n+\t\treturn -ENOEXEC;\n \n \tfmt = get_binfmt_handler(misc, bprm);\n \tif (!fmt)\n-\t\treturn retval;\n+\t\treturn -ENOEXEC;\n \n \t/* Need to be able to load the file after exec */\n-\tretval = -ENOENT;\n \tif (bprm-\u003einterp_flags \u0026 BINPRM_FLAGS_PATH_INACCESSIBLE)\n-\t\tgoto ret;\n+\t\treturn -ENOENT;\n \n \tif (fmt-\u003eflags \u0026 MISC_FMT_PRESERVE_ARGV0) {\n \t\tbprm-\u003einterp_flags |= BINPRM_FLAGS_PRESERVE_ARGV0;\n \t} else {\n \t\tretval = remove_arg_zero(bprm);\n \t\tif (retval)\n-\t\t\tgoto ret;\n+\t\t\treturn retval;\n \t}\n \n \tif (fmt-\u003eflags \u0026 MISC_FMT_OPEN_BINARY)\n@@ -234,57 +252,48 @@ static int load_misc_binary(struct linux_binprm *bprm)\n \t/* make argv[1] be the path to the binary */\n \tretval = copy_string_kernel(bprm-\u003einterp, bprm);\n \tif (retval \u003c 0)\n-\t\tgoto ret;\n+\t\treturn retval;\n \tbprm-\u003eargc++;\n \n \t/* add the interp as argv[0] */\n \tretval = copy_string_kernel(fmt-\u003einterpreter, bprm);\n \tif (retval \u003c 0)\n-\t\tgoto ret;\n+\t\treturn retval;\n \tbprm-\u003eargc++;\n \n \t/* Update interp in case binfmt_script needs it. */\n \tretval = bprm_change_interp(fmt-\u003einterpreter, bprm);\n \tif (retval \u003c 0)\n-\t\tgoto ret;\n+\t\treturn retval;\n \n \tif (fmt-\u003eflags \u0026 MISC_FMT_OPEN_FILE) {\n \t\tinterp_file = file_clone_open(fmt-\u003einterp_file);\n-\t\tif (!IS_ERR(interp_file))\n-\t\t\tdeny_write_access(interp_file);\n+\t\tif (!IS_ERR(interp_file)) {\n+\t\t\tint err = exe_file_deny_write_access(interp_file);\n+\n+\t\t\tif (err) {\n+\t\t\t\tfput(interp_file);\n+\t\t\t\tinterp_file = ERR_PTR(err);\n+\t\t\t}\n+\t\t}\n \t} else {\n \t\tinterp_file = open_exec(fmt-\u003einterpreter);\n \t}\n-\tretval = PTR_ERR(interp_file);\n \tif (IS_ERR(interp_file))\n-\t\tgoto ret;\n+\t\treturn PTR_ERR(interp_file);\n \n \tbprm-\u003einterpreter = interp_file;\n \tif (fmt-\u003eflags \u0026 MISC_FMT_CREDENTIALS)\n \t\tbprm-\u003eexecfd_creds = 1;\n-\n-\tretval = 0;\n-ret:\n-\n-\t/*\n-\t * If we actually put the node here all concurrent calls to\n-\t * load_misc_binary() will have finished. We also know\n-\t * that for the refcount to be zero someone must have concurently\n-\t * removed the binary type handler from the list and it's our job to\n-\t * free it.\n-\t */\n-\tput_binfmt_handler(fmt);\n-\n-\treturn retval;\n+\treturn 0;\n }\n \n /* Command parsers */\n \n /*\n- * parses and copies one argument enclosed in del from *sp to *dp,\n- * recognising the \\x special.\n- * returns pointer to the copied argument or NULL in case of an\n- * error (and sets err) or null argument length.\n+ * Scan the argument starting at @s up to the delimiter @del, recognising\n+ * the \\x escape. Terminates the argument with a NUL and returns a pointer\n+ * past it or NULL on a malformed escape.\n  */\n static char *scanarg(char *s, char del)\n {\n@@ -299,46 +308,123 @@ static char *scanarg(char *s, char del)\n \t\t\t\treturn NULL;\n \t\t}\n \t}\n-\ts[-1] ='\\0';\n+\ts[-1] = '\\0';\n \treturn s;\n }\n \n-static char *check_special_flags(char *sfs, Node *e)\n+static char *check_special_flags(char *p, struct binfmt_misc_entry *e)\n {\n-\tchar *p = sfs;\n-\tint cont = 1;\n-\n-\t/* special flags */\n-\twhile (cont) {\n+\tfor (;; p++) {\n \t\tswitch (*p) {\n \t\tcase 'P':\n \t\t\tpr_debug(\"register: flag: P (preserve argv0)\\n\");\n-\t\t\tp++;\n \t\t\te-\u003eflags |= MISC_FMT_PRESERVE_ARGV0;\n \t\t\tbreak;\n \t\tcase 'O':\n \t\t\tpr_debug(\"register: flag: O (open binary)\\n\");\n-\t\t\tp++;\n \t\t\te-\u003eflags |= MISC_FMT_OPEN_BINARY;\n \t\t\tbreak;\n \t\tcase 'C':\n \t\t\tpr_debug(\"register: flag: C (preserve creds)\\n\");\n-\t\t\tp++;\n-\t\t\t/* this flags also implies the\n-\t\t\t   open-binary flag */\n-\t\t\te-\u003eflags |= (MISC_FMT_CREDENTIALS |\n-\t\t\t\t\tMISC_FMT_OPEN_BINARY);\n+\t\t\t/* C implies O */\n+\t\t\te-\u003eflags |= MISC_FMT_CREDENTIALS | MISC_FMT_OPEN_BINARY;\n \t\t\tbreak;\n \t\tcase 'F':\n \t\t\tpr_debug(\"register: flag: F: open interpreter file now\\n\");\n-\t\t\tp++;\n \t\t\te-\u003eflags |= MISC_FMT_OPEN_FILE;\n \t\t\tbreak;\n \t\tdefault:\n-\t\t\tcont = 0;\n+\t\t\treturn p;\n \t\t}\n \t}\n+}\n+\n+/* Parse the 'offset', 'magic' and 'mask' fields of an 'M' entry. */\n+static char *parse_magic_fields(struct binfmt_misc_entry *e, char *p, char del)\n+{\n+\tchar *s;\n+\n+\t/* Parse the 'offset' field. */\n+\ts = strchr(p, del);\n+\tif (!s)\n+\t\treturn NULL;\n+\t*s = '\\0';\n+\tif (p != s) {\n+\t\tif (kstrtoint(p, 10, \u0026e-\u003eoffset) || e-\u003eoffset \u003c 0)\n+\t\t\treturn NULL;\n+\t}\n+\tp = s + 1;\n+\tpr_debug(\"register: offset: %#x\\n\", e-\u003eoffset);\n+\n+\t/* Parse the 'magic' field. */\n+\te-\u003emagic = p;\n+\tp = scanarg(p, del);\n+\tif (!p || !e-\u003emagic[0])\n+\t\treturn NULL;\n+\tprint_hex_dump_debug(\n+\t\tKBUILD_MODNAME \": register: magic[raw]: \",\n+\t\tDUMP_PREFIX_NONE, 16, 1, e-\u003emagic, p - e-\u003emagic, true);\n+\n+\t/* Parse the 'mask' field. */\n+\te-\u003emask = p;\n+\tp = scanarg(p, del);\n+\tif (!p)\n+\t\treturn NULL;\n+\tif (!e-\u003emask[0]) {\n+\t\te-\u003emask = NULL;\n+\t\tpr_debug(\"register:  mask[raw]: none\\n\");\n+\t} else {\n+\t\tprint_hex_dump_debug(\n+\t\t\tKBUILD_MODNAME \": register:  mask[raw]: \",\n+\t\t\tDUMP_PREFIX_NONE, 16, 1, e-\u003emask, p - e-\u003emask, true);\n+\t}\n+\n+\t/*\n+\t * Decode the magic \u0026 mask fields. Note: while we might have accepted\n+\t * embedded NUL bytes from above, the unescape helpers will stop at\n+\t * the first one they encounter.\n+\t */\n+\te-\u003esize = string_unescape_inplace(e-\u003emagic, UNESCAPE_HEX);\n+\tif (e-\u003emask \u0026\u0026 string_unescape_inplace(e-\u003emask, UNESCAPE_HEX) != e-\u003esize)\n+\t\treturn NULL;\n+\tif (e-\u003esize \u003e BINPRM_BUF_SIZE || BINPRM_BUF_SIZE - e-\u003esize \u003c e-\u003eoffset)\n+\t\treturn NULL;\n+\tpr_debug(\"register: magic/mask length: %i\\n\", e-\u003esize);\n+\tprint_hex_dump_debug(\n+\t\tKBUILD_MODNAME \": register: magic[decoded]: \",\n+\t\tDUMP_PREFIX_NONE, 16, 1, e-\u003emagic, e-\u003esize, true);\n+\tif (e-\u003emask)\n+\t\tprint_hex_dump_debug(\n+\t\t\tKBUILD_MODNAME \": register:  mask[decoded]: \",\n+\t\t\tDUMP_PREFIX_NONE, 16, 1, e-\u003emask, e-\u003esize, true);\n+\treturn p;\n+}\n+\n+/* Parse the 'magic' field of an 'E' entry: the filename extension. */\n+static char *parse_extension_fields(struct binfmt_misc_entry *e, char *p,\n+\t\t\t\t    char del)\n+{\n+\t/* Skip the 'offset' field. */\n+\tp = strchr(p, del);\n+\tif (!p)\n+\t\treturn NULL;\n+\t*p++ = '\\0';\n \n+\t/* Parse the 'magic' field. */\n+\te-\u003emagic = p;\n+\tp = strchr(p, del);\n+\tif (!p)\n+\t\treturn NULL;\n+\t*p++ = '\\0';\n+\tif (!e-\u003emagic[0] || strchr(e-\u003emagic, '/'))\n+\t\treturn NULL;\n+\tpr_debug(\"register: extension: {%s}\\n\", e-\u003emagic);\n+\n+\t/* Skip the 'mask' field. */\n+\tp = strchr(p, del);\n+\tif (!p)\n+\t\treturn NULL;\n+\t*p++ = '\\0';\n \treturn p;\n }\n \n@@ -347,50 +433,48 @@ static char *check_special_flags(char *sfs, Node *e)\n  * ':name:type:offset:magic:mask:interpreter:flags'\n  * where the ':' is the IFS, that can be chosen with the first char\n  */\n-static Node *create_entry(const char __user *buffer, size_t count)\n+static struct binfmt_misc_entry *create_entry(const char __user *buffer,\n+\t\t\t\t\t      size_t count)\n {\n-\tNode *e;\n-\tint memsize, err;\n+\tstruct binfmt_misc_entry *e __free(kfree) = NULL;\n \tchar *buf, *p;\n \tchar del;\n \n \tpr_debug(\"register: received %zu bytes\\n\", count);\n \n \t/* some sanity checks */\n-\terr = -EINVAL;\n \tif ((count \u003c 11) || (count \u003e MAX_REGISTER_LENGTH))\n-\t\tgoto out;\n+\t\treturn ERR_PTR(-EINVAL);\n \n-\terr = -ENOMEM;\n-\tmemsize = sizeof(Node) + count + 8;\n-\te = kmalloc(memsize, GFP_KERNEL_ACCOUNT);\n+\te = kmalloc(struct_size(e, buf, count + MISC_DELIM_PAD),\n+\t\t    GFP_KERNEL_ACCOUNT);\n \tif (!e)\n-\t\tgoto out;\n+\t\treturn ERR_PTR(-ENOMEM);\n \n-\tp = buf = (char *)e + sizeof(Node);\n+\tp = buf = e-\u003ebuf;\n \n-\tmemset(e, 0, sizeof(Node));\n+\tmemset(e, 0, sizeof(*e));\n \tif (copy_from_user(buf, buffer, count))\n-\t\tgoto efault;\n+\t\treturn ERR_PTR(-EFAULT);\n \n-\tdel = *p++;\t/* delimeter */\n+\tdel = *p++;\t/* delimiter */\n \n \tpr_debug(\"register: delim: %#x {%c}\\n\", del, del);\n \n \t/* Pad the buffer with the delim to simplify parsing below. */\n-\tmemset(buf + count, del, 8);\n+\tmemset(buf + count, del, MISC_DELIM_PAD);\n \n \t/* Parse the 'name' field. */\n \te-\u003ename = p;\n \tp = strchr(p, del);\n \tif (!p)\n-\t\tgoto einval;\n+\t\treturn ERR_PTR(-EINVAL);\n \t*p++ = '\\0';\n \tif (!e-\u003ename[0] ||\n \t    !strcmp(e-\u003ename, \".\") ||\n \t    !strcmp(e-\u003ename, \"..\") ||\n \t    strchr(e-\u003ename, '/'))\n-\t\tgoto einval;\n+\t\treturn ERR_PTR(-EINVAL);\n \n \tpr_debug(\"register: name: {%s}\\n\", e-\u003ename);\n \n@@ -398,134 +482,33 @@ static Node *create_entry(const char __user *buffer, size_t count)\n \tswitch (*p++) {\n \tcase 'E':\n \t\tpr_debug(\"register: type: E (extension)\\n\");\n-\t\te-\u003eflags = 1 \u003c\u003c Enabled;\n+\t\te-\u003eflags = BIT(MISC_FMT_ENABLED_BIT);\n \t\tbreak;\n \tcase 'M':\n \t\tpr_debug(\"register: type: M (magic)\\n\");\n-\t\te-\u003eflags = (1 \u003c\u003c Enabled) | (1 \u003c\u003c Magic);\n+\t\te-\u003eflags = BIT(MISC_FMT_ENABLED_BIT) | BIT(MISC_FMT_MAGIC_BIT);\n \t\tbreak;\n \tdefault:\n-\t\tgoto einval;\n+\t\treturn ERR_PTR(-EINVAL);\n \t}\n \tif (*p++ != del)\n-\t\tgoto einval;\n-\n-\tif (test_bit(Magic, \u0026e-\u003eflags)) {\n-\t\t/* Handle the 'M' (magic) format. */\n-\t\tchar *s;\n-\n-\t\t/* Parse the 'offset' field. */\n-\t\ts = strchr(p, del);\n-\t\tif (!s)\n-\t\t\tgoto einval;\n-\t\t*s = '\\0';\n-\t\tif (p != s) {\n-\t\t\tint r = kstrtoint(p, 10, \u0026e-\u003eoffset);\n-\t\t\tif (r != 0 || e-\u003eoffset \u003c 0)\n-\t\t\t\tgoto einval;\n-\t\t}\n-\t\tp = s;\n-\t\tif (*p++)\n-\t\t\tgoto einval;\n-\t\tpr_debug(\"register: offset: %#x\\n\", e-\u003eoffset);\n-\n-\t\t/* Parse the 'magic' field. */\n-\t\te-\u003emagic = p;\n-\t\tp = scanarg(p, del);\n-\t\tif (!p)\n-\t\t\tgoto einval;\n-\t\tif (!e-\u003emagic[0])\n-\t\t\tgoto einval;\n-\t\tif (USE_DEBUG)\n-\t\t\tprint_hex_dump_bytes(\n-\t\t\t\tKBUILD_MODNAME \": register: magic[raw]: \",\n-\t\t\t\tDUMP_PREFIX_NONE, e-\u003emagic, p - e-\u003emagic);\n-\n-\t\t/* Parse the 'mask' field. */\n-\t\te-\u003emask = p;\n-\t\tp = scanarg(p, del);\n-\t\tif (!p)\n-\t\t\tgoto einval;\n-\t\tif (!e-\u003emask[0]) {\n-\t\t\te-\u003emask = NULL;\n-\t\t\tpr_debug(\"register:  mask[raw]: none\\n\");\n-\t\t} else if (USE_DEBUG)\n-\t\t\tprint_hex_dump_bytes(\n-\t\t\t\tKBUILD_MODNAME \": register:  mask[raw]: \",\n-\t\t\t\tDUMP_PREFIX_NONE, e-\u003emask, p - e-\u003emask);\n+\t\treturn ERR_PTR(-EINVAL);\n \n-\t\t/*\n-\t\t * Decode the magic \u0026 mask fields.\n-\t\t * Note: while we might have accepted embedded NUL bytes from\n-\t\t * above, the unescape helpers here will stop at the first one\n-\t\t * it encounters.\n-\t\t */\n-\t\te-\u003esize = string_unescape_inplace(e-\u003emagic, UNESCAPE_HEX);\n-\t\tif (e-\u003emask \u0026\u0026\n-\t\t    string_unescape_inplace(e-\u003emask, UNESCAPE_HEX) != e-\u003esize)\n-\t\t\tgoto einval;\n-\t\tif (e-\u003esize \u003e BINPRM_BUF_SIZE ||\n-\t\t    BINPRM_BUF_SIZE - e-\u003esize \u003c e-\u003eoffset)\n-\t\t\tgoto einval;\n-\t\tpr_debug(\"register: magic/mask length: %i\\n\", e-\u003esize);\n-\t\tif (USE_DEBUG) {\n-\t\t\tprint_hex_dump_bytes(\n-\t\t\t\tKBUILD_MODNAME \": register: magic[decoded]: \",\n-\t\t\t\tDUMP_PREFIX_NONE, e-\u003emagic, e-\u003esize);\n-\n-\t\t\tif (e-\u003emask) {\n-\t\t\t\tint i;\n-\t\t\t\tchar *masked = kmalloc(e-\u003esize, GFP_KERNEL_ACCOUNT);\n-\n-\t\t\t\tprint_hex_dump_bytes(\n-\t\t\t\t\tKBUILD_MODNAME \": register:  mask[decoded]: \",\n-\t\t\t\t\tDUMP_PREFIX_NONE, e-\u003emask, e-\u003esize);\n-\n-\t\t\t\tif (masked) {\n-\t\t\t\t\tfor (i = 0; i \u003c e-\u003esize; ++i)\n-\t\t\t\t\t\tmasked[i] = e-\u003emagic[i] \u0026 e-\u003emask[i];\n-\t\t\t\t\tprint_hex_dump_bytes(\n-\t\t\t\t\t\tKBUILD_MODNAME \": register:  magic[masked]: \",\n-\t\t\t\t\t\tDUMP_PREFIX_NONE, masked, e-\u003esize);\n-\n-\t\t\t\t\tkfree(masked);\n-\t\t\t\t}\n-\t\t\t}\n-\t\t}\n-\t} else {\n-\t\t/* Handle the 'E' (extension) format. */\n-\n-\t\t/* Skip the 'offset' field. */\n-\t\tp = strchr(p, del);\n-\t\tif (!p)\n-\t\t\tgoto einval;\n-\t\t*p++ = '\\0';\n-\n-\t\t/* Parse the 'magic' field. */\n-\t\te-\u003emagic = p;\n-\t\tp = strchr(p, del);\n-\t\tif (!p)\n-\t\t\tgoto einval;\n-\t\t*p++ = '\\0';\n-\t\tif (!e-\u003emagic[0] || strchr(e-\u003emagic, '/'))\n-\t\t\tgoto einval;\n-\t\tpr_debug(\"register: extension: {%s}\\n\", e-\u003emagic);\n-\n-\t\t/* Skip the 'mask' field. */\n-\t\tp = strchr(p, del);\n-\t\tif (!p)\n-\t\t\tgoto einval;\n-\t\t*p++ = '\\0';\n-\t}\n+\tif (test_bit(MISC_FMT_MAGIC_BIT, \u0026e-\u003eflags))\n+\t\tp = parse_magic_fields(e, p, del);\n+\telse\n+\t\tp = parse_extension_fields(e, p, del);\n+\tif (!p)\n+\t\treturn ERR_PTR(-EINVAL);\n \n \t/* Parse the 'interpreter' field. */\n \te-\u003einterpreter = p;\n \tp = strchr(p, del);\n \tif (!p)\n-\t\tgoto einval;\n+\t\treturn ERR_PTR(-EINVAL);\n \t*p++ = '\\0';\n \tif (!e-\u003einterpreter[0])\n-\t\tgoto einval;\n+\t\treturn ERR_PTR(-EINVAL);\n \tpr_debug(\"register: interpreter: {%s}\\n\", e-\u003einterpreter);\n \n \t/* Parse the 'flags' field. */\n@@ -533,24 +516,22 @@ static Node *create_entry(const char __user *buffer, size_t count)\n \tif (*p == '\\n')\n \t\tp++;\n \tif (p != buf + count)\n-\t\tgoto einval;\n-\n-\treturn e;\n-\n-out:\n-\treturn ERR_PTR(err);\n+\t\treturn ERR_PTR(-EINVAL);\n \n-efault:\n-\tkfree(e);\n-\treturn ERR_PTR(-EFAULT);\n-einval:\n-\tkfree(e);\n-\treturn ERR_PTR(-EINVAL);\n+\treturn no_free_ptr(e);\n }\n \n+/* Commands accepted by the /status and /\u003centry\u003e files. */\n+enum bm_command {\n+\tBM_CMD_IGNORE,\t/* empty write */\n+\tBM_CMD_DISABLE,\t/* \"0\" */\n+\tBM_CMD_ENABLE,\t/* \"1\" */\n+\tBM_CMD_REMOVE,\t/* \"-1\" */\n+};\n+\n /*\n- * Set status of entry/binfmt_misc:\n- * '1' enables, '0' disables and '-1' clears entry/binfmt_misc\n+ * Parse what userspace wrote to /status or an entry file: '1' enables,\n+ * '0' disables and '-1' removes the entry or all entries.\n  */\n static int parse_command(const char __user *buffer, size_t count)\n {\n@@ -561,62 +542,64 @@ static int parse_command(const char __user *buffer, size_t count)\n \tif (copy_from_user(s, buffer, count))\n \t\treturn -EFAULT;\n \tif (!count)\n-\t\treturn 0;\n+\t\treturn BM_CMD_IGNORE;\n \tif (s[count - 1] == '\\n')\n \t\tcount--;\n \tif (count == 1 \u0026\u0026 s[0] == '0')\n-\t\treturn 1;\n+\t\treturn BM_CMD_DISABLE;\n \tif (count == 1 \u0026\u0026 s[0] == '1')\n-\t\treturn 2;\n+\t\treturn BM_CMD_ENABLE;\n \tif (count == 2 \u0026\u0026 s[0] == '-' \u0026\u0026 s[1] == '1')\n-\t\treturn 3;\n+\t\treturn BM_CMD_REMOVE;\n \treturn -EINVAL;\n }\n \n /* generic stuff */\n \n-static void entry_status(Node *e, char *page)\n+static void bm_seq_hex(struct seq_file *m, const u8 *data, int size)\n {\n-\tchar *dp = page;\n-\tconst char *status = \"disabled\";\n+\tfor (int i = 0; i \u003c size; i++)\n+\t\tseq_printf(m, \"%02x\", data[i]);\n+}\n \n-\tif (test_bit(Enabled, \u0026e-\u003eflags))\n-\t\tstatus = \"enabled\";\n+static int bm_entry_show(struct seq_file *m, void *unused)\n+{\n+\tstruct binfmt_misc_entry *e = m-\u003eprivate;\n \n-\tif (!VERBOSE_STATUS) {\n-\t\tsprintf(page, \"%s\\n\", status);\n-\t\treturn;\n-\t}\n+\tif (test_bit(MISC_FMT_ENABLED_BIT, \u0026e-\u003eflags))\n+\t\tseq_puts(m, \"enabled\\n\");\n+\telse\n+\t\tseq_puts(m, \"disabled\\n\");\n \n-\tdp += sprintf(dp, \"%s\\ninterpreter %s\\n\", status, e-\u003einterpreter);\n+\tseq_printf(m, \"interpreter %s\\n\", e-\u003einterpreter);\n \n \t/* print the special flags */\n-\tdp += sprintf(dp, \"flags: \");\n+\tseq_puts(m, \"flags: \");\n \tif (e-\u003eflags \u0026 MISC_FMT_PRESERVE_ARGV0)\n-\t\t*dp++ = 'P';\n+\t\tseq_putc(m, 'P');\n \tif (e-\u003eflags \u0026 MISC_FMT_OPEN_BINARY)\n-\t\t*dp++ = 'O';\n+\t\tseq_putc(m, 'O');\n \tif (e-\u003eflags \u0026 MISC_FMT_CREDENTIALS)\n-\t\t*dp++ = 'C';\n+\t\tseq_putc(m, 'C');\n \tif (e-\u003eflags \u0026 MISC_FMT_OPEN_FILE)\n-\t\t*dp++ = 'F';\n-\t*dp++ = '\\n';\n+\t\tseq_putc(m, 'F');\n+\tseq_putc(m, '\\n');\n \n-\tif (!test_bit(Magic, \u0026e-\u003eflags)) {\n-\t\tsprintf(dp, \"extension .%s\\n\", e-\u003emagic);\n+\tif (!test_bit(MISC_FMT_MAGIC_BIT, \u0026e-\u003eflags)) {\n+\t\tseq_printf(m, \"extension .%s\\n\", e-\u003emagic);\n \t} else {\n-\t\tdp += sprintf(dp, \"offset %i\\nmagic \", e-\u003eoffset);\n-\t\tdp = bin2hex(dp, e-\u003emagic, e-\u003esize);\n+\t\tseq_printf(m, \"offset %i\\nmagic \", e-\u003eoffset);\n+\t\tbm_seq_hex(m, e-\u003emagic, e-\u003esize);\n \t\tif (e-\u003emask) {\n-\t\t\tdp += sprintf(dp, \"\\nmask \");\n-\t\t\tdp = bin2hex(dp, e-\u003emask, e-\u003esize);\n+\t\t\tseq_puts(m, \"\\nmask \");\n+\t\t\tbm_seq_hex(m, e-\u003emask, e-\u003esize);\n \t\t}\n-\t\t*dp++ = '\\n';\n-\t\t*dp = '\\0';\n+\t\tseq_putc(m, '\\n');\n \t}\n+\treturn 0;\n }\n \n-static struct inode *bm_get_inode(struct super_block *sb, int mode)\n+static struct inode *bm_get_inode(struct super_block *sb, umode_t mode)\n {\n \tstruct inode *inode = new_inode(sb);\n \n@@ -652,14 +635,14 @@ static struct binfmt_misc *i_binfmt_misc(struct inode *inode)\n  * entry is removed or the filesystem is unmounted and the super block is\n  * shutdown.\n  *\n- * If the -\u003eevict call was not caused by a super block shutdown but by a write\n- * to remove the entry or all entries via bm_{entry,status}_write() the entry\n- * will have already been removed from the list. We keep the list_empty() check\n- * to make that explicit.\n+ * If the -\u003eevict call was not caused by a super block shutdown but by\n+ * removing the entry via bm_{entry,status}_write() or unlink(2) the entry\n+ * will have already been removed from the list. We keep the hlist_unhashed()\n+ * check to make that explicit.\n */\n static void bm_evict_inode(struct inode *inode)\n {\n-\tNode *e = inode-\u003ei_private;\n+\tstruct binfmt_misc_entry *e = inode-\u003ei_private;\n \n \tclear_inode(inode);\n \n@@ -667,89 +650,132 @@ static void bm_evict_inode(struct inode *inode)\n \t\tstruct binfmt_misc *misc;\n \n \t\tmisc = i_binfmt_misc(inode);\n-\t\twrite_lock(\u0026misc-\u003eentries_lock);\n-\t\tif (!list_empty(\u0026e-\u003elist))\n-\t\t\tlist_del_init(\u0026e-\u003elist);\n-\t\twrite_unlock(\u0026misc-\u003eentries_lock);\n+\t\tspin_lock(\u0026misc-\u003eentries_lock);\n+\t\tif (!hlist_unhashed(\u0026e-\u003enode))\n+\t\t\thlist_del_init_rcu(\u0026e-\u003enode);\n+\t\tspin_unlock(\u0026misc-\u003eentries_lock);\n \t\tput_binfmt_handler(e);\n \t}\n }\n \n+/**\n+ * unlink_binfmt_handler - unhash a binary type handler\n+ * @misc: handle to binfmt_misc instance\n+ * @e: binary type handler to unhash\n+ *\n+ * Adding and removing entries via bm_{entry,register,status}_write() and\n+ * unlink(2) happens under the exclusively held inode lock of the root\n+ * dentry keeping the list stable for writers. load_misc_binary() walks it\n+ * concurrently under RCU. The entries_lock is only held around the actual\n+ * unlink to serialize against bm_evict_inode() which unlinks entries\n+ * during umount without holding the root inode lock.\n+ */\n+static void unlink_binfmt_handler(struct binfmt_misc *misc,\n+\t\t\t\t  struct binfmt_misc_entry *e)\n+{\n+\tspin_lock(\u0026misc-\u003eentries_lock);\n+\thlist_del_init_rcu(\u0026e-\u003enode);\n+\tspin_unlock(\u0026misc-\u003eentries_lock);\n+}\n+\n /**\n  * remove_binfmt_handler - remove a binary type handler\n  * @misc: handle to binfmt_misc instance\n  * @e: binary type handler to remove\n  *\n  * Remove a binary type handler from the list of binary type handlers and\n- * remove its associated dentry. This is called from\n- * binfmt_{entry,status}_write(). In the future, we might want to think about\n- * adding a proper -\u003eunlink() method to binfmt_misc instead of forcing caller's\n- * to use writes to files in order to delete binary type handlers. But it has\n- * worked for so long that it's not a pressing issue.\n+ * remove its associated dentry.\n  */\n-static void remove_binfmt_handler(struct binfmt_misc *misc, Node *e)\n+static void remove_binfmt_handler(struct binfmt_misc *misc,\n+\t\t\t\t  struct binfmt_misc_entry *e)\n {\n-\twrite_lock(\u0026misc-\u003eentries_lock);\n-\tlist_del_init(\u0026e-\u003elist);\n-\twrite_unlock(\u0026misc-\u003eentries_lock);\n+\tunlink_binfmt_handler(misc, e);\n \tlocked_recursive_removal(e-\u003edentry, NULL);\n }\n \n-/* /\u003centry\u003e */\n+/* Remove @e unless it was already removed. */\n+static void bm_remove_entry(struct binfmt_misc_entry *e, struct super_block *sb)\n+{\n+\tstruct inode *root = d_inode(sb-\u003es_root);\n \n-static ssize_t\n-bm_entry_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)\n+\tinode_lock_nested(root, I_MUTEX_PARENT);\n+\tif (!hlist_unhashed(\u0026e-\u003enode))\n+\t\tremove_binfmt_handler(i_binfmt_misc(root), e);\n+\tinode_unlock(root);\n+}\n+\n+/* Remove all entries of the binfmt_misc instance @misc belonging to @sb. */\n+static void bm_remove_all_entries(struct binfmt_misc *misc,\n+\t\t\t\t  struct super_block *sb)\n {\n-\tNode *e = file_inode(file)-\u003ei_private;\n-\tssize_t res;\n-\tchar *page;\n+\tstruct inode *root = d_inode(sb-\u003es_root);\n+\tstruct binfmt_misc_entry *e;\n+\tstruct hlist_node *next;\n+\n+\tinode_lock_nested(root, I_MUTEX_PARENT);\n+\thlist_for_each_entry_safe(e, next, \u0026misc-\u003eentries, node)\n+\t\tremove_binfmt_handler(misc, e);\n+\tinode_unlock(root);\n+}\n \n-\tpage = kmalloc(PAGE_SIZE, GFP_KERNEL);\n-\tif (!page)\n-\t\treturn -ENOMEM;\n+/**\n+ * bm_unlink - remove a binary type handler via unlink(2)\n+ * @dir: inode of the root directory\n+ * @dentry: entry file to remove\n+ *\n+ * Removing the entry file removes its binary type handler, exactly like\n+ * writing -1 to it does. The status and register control files can't be\n+ * removed. The VFS calls this with the root inode lock held which\n+ * serializes against the write based add and remove paths.\n+ */\n+static int bm_unlink(struct inode *dir, struct dentry *dentry)\n+{\n+\tstruct binfmt_misc_entry *e = d_inode(dentry)-\u003ei_private;\n+\n+\tif (!e)\n+\t\treturn -EPERM;\n+\n+\tunlink_binfmt_handler(i_binfmt_misc(dir), e);\n+\treturn simple_unlink(dir, dentry);\n+}\n \n-\tentry_status(e, page);\n+static const struct inode_operations bm_dir_inode_operations = {\n+\t.lookup\t\t= simple_lookup,\n+\t.unlink\t\t= bm_unlink,\n+};\n \n-\tres = simple_read_from_buffer(buf, nbytes, ppos, page, strlen(page));\n+/* /\u003centry\u003e */\n+\n+static int bm_entry_open(struct inode *inode, struct file *file)\n+{\n+\tint ret;\n \n-\tkfree(page);\n-\treturn res;\n+\tret = single_open(file, bm_entry_show, inode-\u003ei_private);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* seq_open() clears FMODE_PWRITE, bm_entry_write() takes any offset */\n+\tif (file-\u003ef_mode \u0026 FMODE_WRITE)\n+\t\tfile-\u003ef_mode |= FMODE_PWRITE;\n+\treturn 0;\n }\n \n static ssize_t bm_entry_write(struct file *file, const char __user *buffer,\n \t\t\t\tsize_t count, loff_t *ppos)\n {\n \tstruct inode *inode = file_inode(file);\n-\tNode *e = inode-\u003ei_private;\n+\tstruct binfmt_misc_entry *e = inode-\u003ei_private;\n \tint res = parse_command(buffer, count);\n \n \tswitch (res) {\n-\tcase 1:\n-\t\t/* Disable this handler. */\n-\t\tclear_bit(Enabled, \u0026e-\u003eflags);\n+\tcase BM_CMD_DISABLE:\n+\t\tclear_bit(MISC_FMT_ENABLED_BIT, \u0026e-\u003eflags);\n \t\tbreak;\n-\tcase 2:\n-\t\t/* Enable this handler. */\n-\t\tset_bit(Enabled, \u0026e-\u003eflags);\n+\tcase BM_CMD_ENABLE:\n+\t\tset_bit(MISC_FMT_ENABLED_BIT, \u0026e-\u003eflags);\n \t\tbreak;\n-\tcase 3:\n-\t\t/* Delete this handler. */\n-\t\tinode = d_inode(inode-\u003ei_sb-\u003es_root);\n-\t\tinode_lock_nested(inode, I_MUTEX_PARENT);\n-\n-\t\t/*\n-\t\t * In order to add new element or remove elements from the list\n-\t\t * via bm_{entry,register,status}_write() inode_lock() on the\n-\t\t * root inode must be held.\n-\t\t * The lock is exclusive ensuring that the list can't be\n-\t\t * modified. Only load_misc_binary() can access but does so\n-\t\t * read-only. So we only need to take the write lock when we\n-\t\t * actually remove the entry from the list.\n-\t\t */\n-\t\tif (!list_empty(\u0026e-\u003elist))\n-\t\t\tremove_binfmt_handler(i_binfmt_misc(inode), e);\n-\n-\t\tinode_unlock(inode);\n+\tcase BM_CMD_REMOVE:\n+\t\tbm_remove_entry(e, inode-\u003ei_sb);\n \t\tbreak;\n \tdefault:\n \t\treturn res;\n@@ -759,15 +785,17 @@ static ssize_t bm_entry_write(struct file *file, const char __user *buffer,\n }\n \n static const struct file_operations bm_entry_operations = {\n-\t.read\t\t= bm_entry_read,\n+\t.open\t\t= bm_entry_open,\n+\t.read\t\t= seq_read,\n \t.write\t\t= bm_entry_write,\n-\t.llseek\t\t= default_llseek,\n+\t.llseek\t\t= seq_lseek,\n+\t.release\t= single_release,\n };\n \n /* /register */\n \n /* add to filesystem */\n-static int add_entry(Node *e, struct super_block *sb)\n+static int add_entry(struct binfmt_misc_entry *e, struct super_block *sb)\n {\n \tstruct dentry *dentry = simple_start_creating(sb-\u003es_root, e-\u003ename);\n \tstruct inode *inode;\n@@ -789,9 +817,9 @@ static int add_entry(Node *e, struct super_block *sb)\n \n \td_make_persistent(dentry, inode);\n \tmisc = i_binfmt_misc(inode);\n-\twrite_lock(\u0026misc-\u003eentries_lock);\n-\tlist_add(\u0026e-\u003elist, \u0026misc-\u003eentries);\n-\twrite_unlock(\u0026misc-\u003eentries_lock);\n+\tspin_lock(\u0026misc-\u003eentries_lock);\n+\thlist_add_head_rcu(\u0026e-\u003enode, \u0026misc-\u003eentries);\n+\tspin_unlock(\u0026misc-\u003eentries_lock);\n \tsimple_done_creating(dentry);\n \treturn 0;\n }\n@@ -799,13 +827,12 @@ static int add_entry(Node *e, struct super_block *sb)\n static ssize_t bm_register_write(struct file *file, const char __user *buffer,\n \t\t\t       size_t count, loff_t *ppos)\n {\n-\tNode *e;\n+\tstruct binfmt_misc_entry *e __free(kfree) = NULL;\n \tstruct super_block *sb = file_inode(file)-\u003ei_sb;\n-\tint err = 0;\n \tstruct file *f = NULL;\n+\tint err;\n \n \te = create_entry(buffer, count);\n-\n \tif (IS_ERR(e))\n \t\treturn PTR_ERR(e);\n \n@@ -822,7 +849,6 @@ static ssize_t bm_register_write(struct file *file, const char __user *buffer,\n \t\tif (IS_ERR(f)) {\n \t\t\tpr_notice(\"register: failed to install interpreter file %s\\n\",\n \t\t\t\t e-\u003einterpreter);\n-\t\t\tkfree(e);\n \t\t\treturn PTR_ERR(f);\n \t\t}\n \t\te-\u003einterp_file = f;\n@@ -834,9 +860,11 @@ 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\tkfree(e);\n \t\treturn err;\n \t}\n+\n+\t/* The entry is owned by its inode now. */\n+\tretain_and_null_ptr(e);\n \treturn count;\n }\n \n@@ -851,10 +879,10 @@ static ssize_t\n bm_status_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)\n {\n \tstruct binfmt_misc *misc;\n-\tchar *s;\n+\tconst char *s;\n \n \tmisc = i_binfmt_misc(file_inode(file));\n-\ts = misc-\u003eenabled ? \"enabled\\n\" : \"disabled\\n\";\n+\ts = READ_ONCE(misc-\u003eenabled) ? \"enabled\\n\" : \"disabled\\n\";\n \treturn simple_read_from_buffer(buf, nbytes, ppos, s, strlen(s));\n }\n \n@@ -863,37 +891,17 @@ static ssize_t bm_status_write(struct file *file, const char __user *buffer,\n {\n \tstruct binfmt_misc *misc;\n \tint res = parse_command(buffer, count);\n-\tNode *e, *next;\n-\tstruct inode *inode;\n \n \tmisc = i_binfmt_misc(file_inode(file));\n \tswitch (res) {\n-\tcase 1:\n-\t\t/* Disable all handlers. */\n-\t\tmisc-\u003eenabled = false;\n+\tcase BM_CMD_DISABLE:\n+\t\tWRITE_ONCE(misc-\u003eenabled, false);\n \t\tbreak;\n-\tcase 2:\n-\t\t/* Enable all handlers. */\n-\t\tmisc-\u003eenabled = true;\n+\tcase BM_CMD_ENABLE:\n+\t\tWRITE_ONCE(misc-\u003eenabled, true);\n \t\tbreak;\n-\tcase 3:\n-\t\t/* Delete all handlers. */\n-\t\tinode = d_inode(file_inode(file)-\u003ei_sb-\u003es_root);\n-\t\tinode_lock_nested(inode, I_MUTEX_PARENT);\n-\n-\t\t/*\n-\t\t * In order to add new element or remove elements from the list\n-\t\t * via bm_{entry,register,status}_write() inode_lock() on the\n-\t\t * root inode must be held.\n-\t\t * The lock is exclusive ensuring that the list can't be\n-\t\t * modified. Only load_misc_binary() can access but does so\n-\t\t * read-only. So we only need to take the write lock when we\n-\t\t * actually remove the entry from the list.\n-\t\t */\n-\t\tlist_for_each_entry_safe(e, next, \u0026misc-\u003eentries, list)\n-\t\t\tremove_binfmt_handler(misc, e);\n-\n-\t\tinode_unlock(inode);\n+\tcase BM_CMD_REMOVE:\n+\t\tbm_remove_all_entries(misc, file_inode(file)-\u003ei_sb);\n \t\tbreak;\n \tdefault:\n \t\treturn res;\n@@ -918,7 +926,7 @@ static void bm_put_super(struct super_block *sb)\n \tput_user_ns(user_ns);\n }\n \n-static const struct super_operations s_ops = {\n+static const struct super_operations bm_super_ops = {\n \t.statfs\t\t= simple_statfs,\n \t.evict_inode\t= bm_evict_inode,\n \t.put_super\t= bm_put_super,\n@@ -965,10 +973,10 @@ static int bm_fill_super(struct super_block *sb, struct fs_context *fc)\n \t\tif (!misc)\n \t\t\treturn -ENOMEM;\n \n-\t\tINIT_LIST_HEAD(\u0026misc-\u003eentries);\n-\t\trwlock_init(\u0026misc-\u003eentries_lock);\n+\t\tINIT_HLIST_HEAD(\u0026misc-\u003eentries);\n+\t\tspin_lock_init(\u0026misc-\u003eentries_lock);\n \n-\t\t/* Pairs with smp_load_acquire() in load_binfmt_misc(). */\n+\t\t/* Pairs with smp_load_acquire() in current_binfmt_misc(). */\n \t\tsmp_store_release(\u0026user_ns-\u003ebinfmt_misc, misc);\n \t}\n \n@@ -983,12 +991,15 @@ static int bm_fill_super(struct super_block *sb, struct fs_context *fc)\n \t * someone mounts binfmt_misc for the first time or again we simply\n \t * reset -\u003eenabled to true.\n \t */\n-\tmisc-\u003eenabled = true;\n+\tWRITE_ONCE(misc-\u003eenabled, true);\n \n \terr = simple_fill_super(sb, BINFMTFS_MAGIC, bm_files);\n-\tif (!err)\n-\t\tsb-\u003es_op = \u0026s_ops;\n-\treturn err;\n+\tif (err)\n+\t\treturn err;\n+\n+\tsb-\u003es_op = \u0026bm_super_ops;\n+\td_inode(sb-\u003es_root)-\u003ei_op = \u0026bm_dir_inode_operations;\n+\treturn 0;\n }\n \n static void bm_free(struct fs_context *fc)\ndiff --git a/include/linux/binfmts.h b/include/linux/binfmts.h\nindex 2c77e383e73757..7e7333b7bb0f11 100644\n--- a/include/linux/binfmts.h\n+++ b/include/linux/binfmts.h\n@@ -101,8 +101,8 @@ struct linux_binfmt {\n \n #if IS_ENABLED(CONFIG_BINFMT_MISC)\n struct binfmt_misc {\n-\tstruct list_head entries;\n-\trwlock_t entries_lock;\n+\tstruct hlist_head entries;\n+\tspinlock_t entries_lock;\n \tbool enabled;\n } __randomize_layout;\n \ndiff --git a/kernel/user.c b/kernel/user.c\nindex 7aef4e679a6a0c..21bafdc1137956 100644\n--- a/kernel/user.c\n+++ b/kernel/user.c\n@@ -23,9 +23,9 @@\n \n #if IS_ENABLED(CONFIG_BINFMT_MISC)\n struct binfmt_misc init_binfmt_misc = {\n-\t.entries = LIST_HEAD_INIT(init_binfmt_misc.entries),\n+\t.entries = HLIST_HEAD_INIT,\n \t.enabled = true,\n-\t.entries_lock = __RW_LOCK_UNLOCKED(init_binfmt_misc.entries_lock),\n+\t.entries_lock = __SPIN_LOCK_UNLOCKED(init_binfmt_misc.entries_lock),\n };\n EXPORT_SYMBOL_GPL(init_binfmt_misc);\n #endif\n"
}

2/1 2026/07/10 20:32 agent patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "BINFMT_MISC"
  ],
  "FocusSymbols": [
    "load_misc_binary",
    "bm_register_write",
    "bm_entry_write",
    "bm_status_write",
    "bm_unlink",
    "bm_entry_show"
  ],
  "Reasoning": "The patch significantly refactors the binfmt_misc filesystem, introducing RCU-based lookups, changing internal locking from rwlock to spinlock, converting entry reading to use seq_file, and adding support for removing entries via unlink(2). These are functional changes to reachable core kernel logic that should be fuzzed to ensure no regressions, memory leaks, or concurrency issues were introduced.",
  "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 a877db1bfb7d2d8a5bcfea03f3fc112c8403ad32
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Jul 10 20:32:37 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..306ef48f5de6dd 100644
--- a/Documentation/admin-guide/binfmt-misc.rst
+++ b/Documentation/admin-guide/binfmt-misc.rst
@@ -133,7 +133,8 @@ 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``.
 
 
 Hints
diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c
index 84349fcb93f13a..f51d2726986160 100644
--- a/fs/binfmt_misc.c
+++ b/fs/binfmt_misc.c
@@ -10,45 +10,46 @@
 
 #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/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/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/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
-
-enum {
-	VERBOSE_STATUS = 1 /* make it zero to save 400 bytes kernel memory */
+/* Entry status and match type bit numbers. */
+enum binfmt_misc_entry_bits {
+	MISC_FMT_ENABLED_BIT	= 0,
+	MISC_FMT_MAGIC_BIT	= 1,
 };
 
-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)
+/* 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),
+};
 
-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 */
@@ -59,9 +60,9 @@ typedef struct {
 	struct dentry *dentry;
 	struct file *interp_file;
 	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,10 +75,36 @@ 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
+
+/* 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
@@ -86,43 +113,30 @@ static struct file_system_type bm_fs_type;
  * Search for a binary type handler for @bprm in the list of registered binary
  * type handlers.
  *
+ * The caller must hold the RCU read lock.
+ *
  * Return: 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;
-
+	hlist_for_each_entry_rcu(e, &misc->entries, node) {
 		/* Make sure this one is currently enabled. */
-		if (!test_bit(Enabled, &e->flags))
+		if (!test_bit(MISC_FMT_ENABLED_BIT, &e->flags))
 			continue;
 
-		/* Do matching based on extension if applicable. */
-		if (!test_bit(Magic, &e->flags)) {
-			if (p && !strcmp(e->magic, p + 1))
+		if (test_bit(MISC_FMT_MAGIC_BIT, &e->flags)) {
+			if (entry_matches_magic(e, bprm))
 				return e;
-			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;
 		} else {
-			for (j = 0; j < e->size; j++)
-				if ((*s++ ^ e->magic[j]))
-					break;
+			if (entry_matches_extension(e, ext))
+				return e;
 		}
-		if (j == e->size)
-			return e;
 	}
 
 	return NULL;
@@ -134,64 +148,69 @@ static Node *search_binfmt_handler(struct binfmt_misc *misc,
  * @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().
+ * reference to protect against removal via bm_{entry,status}_write(). The
+ * refcount of an entry can only drop to zero once it has been unlinked and
+ * a restarted search cannot find an unlinked entry again so the retry loop
+ * is bounded.
  *
  * 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;
+	struct binfmt_misc_entry *e;
 
-	read_lock(&misc->entries_lock);
-	e = search_binfmt_handler(misc, bprm);
-	if (e)
-		refcount_inc(&e->users);
-	read_unlock(&misc->entries_lock);
+	guard(rcu)();
+	do {
+		e = search_binfmt_handler(misc, bprm);
+	} while (e && !refcount_inc_not_zero(&e->users));
 	return e;
 }
 
 /**
- * put_binfmt_handler - put binary handler node
- * @e: node to put
+ * put_binfmt_handler - put binary handler entry
+ * @e: entry to put
  *
- * Free node syncing with load_misc_binary() and defer final free to
+ * 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.
  */
-static void put_binfmt_handler(Node *e)
+static void put_binfmt_handler(struct binfmt_misc_entry *e)
 {
 	if (refcount_dec_and_test(&e->users)) {
-		if (e->flags & MISC_FMT_OPEN_FILE)
+		if (e->flags & MISC_FMT_OPEN_FILE) {
+			exe_file_allow_write_access(e->interp_file);
 			filp_close(e->interp_file, NULL);
-		kfree(e);
+		}
+		/* Lockless walkers may still dereference this entry. */
+		kfree_rcu(e, rcu);
 	}
 }
 
+DEFINE_FREE(put_binfmt_handler, struct binfmt_misc_entry *, if (_T) put_binfmt_handler(_T))
+
 /**
- * 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;
@@ -202,30 +221,29 @@ static struct binfmt_misc *load_binfmt_misc(void)
  */
 static int load_misc_binary(struct linux_binprm *bprm)
 {
-	Node *fmt;
-	struct file *interp_file = NULL;
-	int retval = -ENOEXEC;
+	struct binfmt_misc_entry *fmt __free(put_binfmt_handler) = NULL;
+	struct file *interp_file;
 	struct binfmt_misc *misc;
+	int retval;
 
-	misc = load_binfmt_misc();
-	if (!misc->enabled)
-		return retval;
+	misc = current_binfmt_misc();
+	if (!READ_ONCE(misc->enabled))
+		return -ENOEXEC;
 
 	fmt = get_binfmt_handler(misc, bprm);
 	if (!fmt)
-		return retval;
+		return -ENOEXEC;
 
 	/* Need to be able to load the file after exec */
-	retval = -ENOENT;
 	if (bprm->interp_flags & BINPRM_FLAGS_PATH_INACCESSIBLE)
-		goto ret;
+		return -ENOENT;
 
 	if (fmt->flags & MISC_FMT_PRESERVE_ARGV0) {
 		bprm->interp_flags |= BINPRM_FLAGS_PRESERVE_ARGV0;
 	} else {
 		retval = remove_arg_zero(bprm);
 		if (retval)
-			goto ret;
+			return retval;
 	}
 
 	if (fmt->flags & MISC_FMT_OPEN_BINARY)
@@ -234,57 +252,48 @@ static int load_misc_binary(struct linux_binprm *bprm)
 	/* make argv[1] be the path to the binary */
 	retval = copy_string_kernel(bprm->interp, bprm);
 	if (retval < 0)
-		goto ret;
+		return retval;
 	bprm->argc++;
 
 	/* add the interp as argv[0] */
 	retval = copy_string_kernel(fmt->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 retval;
 
 	if (fmt->flags & MISC_FMT_OPEN_FILE) {
 		interp_file = file_clone_open(fmt->interp_file);
-		if (!IS_ERR(interp_file))
-			deny_write_access(interp_file);
+		if (!IS_ERR(interp_file)) {
+			int err = exe_file_deny_write_access(interp_file);
+
+			if (err) {
+				fput(interp_file);
+				interp_file = ERR_PTR(err);
+			}
+		}
 	} else {
 		interp_file = open_exec(fmt->interpreter);
 	}
-	retval = PTR_ERR(interp_file);
 	if (IS_ERR(interp_file))
-		goto ret;
+		return PTR_ERR(interp_file);
 
 	bprm->interpreter = interp_file;
 	if (fmt->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)
 {
@@ -299,46 +308,123 @@ 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)
+static char *check_special_flags(char *p, struct binfmt_misc_entry *e)
 {
-	char *p = sfs;
-	int cont = 1;
-
-	/* special flags */
-	while (cont) {
+	for (;; p++) {
 		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);
+			/* C implies O */
+			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;
+			return p;
 		}
 	}
+}
+
+/* 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;
 }
 
@@ -347,50 +433,48 @@ 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));
 	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);
 
 	/* 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);
 
@@ -398,134 +482,33 @@ 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;
 	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);
+		return ERR_PTR(-EINVAL);
 
-		/*
-		 * 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';
-	}
+	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;
+		return ERR_PTR(-EINVAL);
 	pr_debug("register: interpreter: {%s}\n", e->interpreter);
 
 	/* Parse the 'flags' field. */
@@ -533,24 +516,22 @@ static Node *create_entry(const char __user *buffer, size_t count)
 	if (*p == '\n')
 		p++;
 	if (p != buf + count)
-		goto einval;
-
-	return e;
-
-out:
-	return ERR_PTR(err);
+		return ERR_PTR(-EINVAL);
 
-efault:
-	kfree(e);
-	return ERR_PTR(-EFAULT);
-einval:
-	kfree(e);
-	return ERR_PTR(-EINVAL);
+	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" */
+};
+
 /*
- * 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)
 {
@@ -561,62 +542,64 @@ static int parse_command(const char __user *buffer, size_t count)
 	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;
 }
 
 /* generic stuff */
 
-static void entry_status(Node *e, char *page)
+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);
+	seq_printf(m, "interpreter %s\n", e->interpreter);
 
 	/* print the special flags */
-	dp += sprintf(dp, "flags: ");
+	seq_puts(m, "flags: ");
 	if (e->flags & MISC_FMT_PRESERVE_ARGV0)
-		*dp++ = 'P';
+		seq_putc(m, 'P');
 	if (e->flags & MISC_FMT_OPEN_BINARY)
-		*dp++ = 'O';
+		seq_putc(m, 'O');
 	if (e->flags & MISC_FMT_CREDENTIALS)
-		*dp++ = 'C';
+		seq_putc(m, 'C');
 	if (e->flags & MISC_FMT_OPEN_FILE)
-		*dp++ = 'F';
-	*dp++ = '\n';
+		seq_putc(m, 'F');
+	seq_putc(m, '\n');
 
-	if (!test_bit(Magic, &e->flags)) {
-		sprintf(dp, "extension .%s\n", e->magic);
+	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);
 
@@ -652,14 +635,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);
 
@@ -667,89 +650,132 @@ 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 RCU. 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);
 }
 
-/* /<entry> */
+/* Remove @e unless it was already removed. */
+static void bm_remove_entry(struct binfmt_misc_entry *e, struct super_block *sb)
+{
+	struct inode *root = d_inode(sb->s_root);
 
-static ssize_t
-bm_entry_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
+	inode_lock_nested(root, I_MUTEX_PARENT);
+	if (!hlist_unhashed(&e->node))
+		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)
 {
-	Node *e = file_inode(file)->i_private;
-	ssize_t res;
-	char *page;
+	struct inode *root = d_inode(sb->s_root);
+	struct binfmt_misc_entry *e;
+	struct hlist_node *next;
+
+	inode_lock_nested(root, I_MUTEX_PARENT);
+	hlist_for_each_entry_safe(e, next, &misc->entries, node)
+		remove_binfmt_handler(misc, e);
+	inode_unlock(root);
+}
 
-	page = kmalloc(PAGE_SIZE, GFP_KERNEL);
-	if (!page)
-		return -ENOMEM;
+/**
+ * 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);
+}
 
-	entry_status(e, page);
+static const struct inode_operations bm_dir_inode_operations = {
+	.lookup		= simple_lookup,
+	.unlink		= bm_unlink,
+};
 
-	res = simple_read_from_buffer(buf, nbytes, ppos, page, strlen(page));
+/* /<entry> */
+
+static int bm_entry_open(struct inode *inode, struct file *file)
+{
+	int ret;
 
-	kfree(page);
-	return res;
+	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;
 }
 
 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;
+	struct binfmt_misc_entry *e = inode->i_private;
 	int res = parse_command(buffer, count);
 
 	switch (res) {
-	case 1:
-		/* Disable this handler. */
-		clear_bit(Enabled, &e->flags);
+	case BM_CMD_DISABLE:
+		clear_bit(MISC_FMT_ENABLED_BIT, &e->flags);
 		break;
-	case 2:
-		/* Enable this handler. */
-		set_bit(Enabled, &e->flags);
+	case BM_CMD_ENABLE:
+		set_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);
-
-		/*
-		 * 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.
-		 */
-		if (!list_empty(&e->list))
-			remove_binfmt_handler(i_binfmt_misc(inode), e);
-
-		inode_unlock(inode);
+	case BM_CMD_REMOVE:
+		bm_remove_entry(e, inode->i_sb);
 		break;
 	default:
 		return res;
@@ -759,15 +785,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;
@@ -789,9 +817,9 @@ static int add_entry(Node *e, struct super_block *sb)
 
 	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);
+	spin_lock(&misc->entries_lock);
+	hlist_add_head_rcu(&e->node, &misc->entries);
+	spin_unlock(&misc->entries_lock);
 	simple_done_creating(dentry);
 	return 0;
 }
@@ -799,13 +827,12 @@ 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(kfree) = 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);
 
@@ -822,7 +849,6 @@ static ssize_t bm_register_write(struct file *file, const char __user *buffer,
 		if (IS_ERR(f)) {
 			pr_notice("register: failed to install interpreter file %s\n",
 				 e->interpreter);
-			kfree(e);
 			return PTR_ERR(f);
 		}
 		e->interp_file = f;
@@ -834,9 +860,11 @@ static ssize_t bm_register_write(struct file *file, const char __user *buffer,
 			exe_file_allow_write_access(f);
 			filp_close(f, NULL);
 		}
-		kfree(e);
 		return err;
 	}
+
+	/* The entry is owned by its inode now. */
+	retain_and_null_ptr(e);
 	return count;
 }
 
@@ -851,10 +879,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));
 }
 
@@ -863,37 +891,17 @@ static ssize_t bm_status_write(struct file *file, const char __user *buffer,
 {
 	struct binfmt_misc *misc;
 	int res = parse_command(buffer, count);
-	Node *e, *next;
-	struct inode *inode;
 
 	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;
@@ -918,7 +926,7 @@ static void bm_put_super(struct super_block *sb)
 	put_user_ns(user_ns);
 }
 
-static const struct super_operations s_ops = {
+static const struct super_operations bm_super_ops = {
 	.statfs		= simple_statfs,
 	.evict_inode	= bm_evict_inode,
 	.put_super	= bm_put_super,
@@ -965,10 +973,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);
 	}
 
@@ -983,12 +991,15 @@ static int bm_fill_super(struct super_block *sb, struct fs_context *fc)
 	 * 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)
diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h
index 2c77e383e73757..7e7333b7bb0f11 100644
--- a/include/linux/binfmts.h
+++ b/include/linux/binfmts.h
@@ -101,8 +101,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;
 
diff --git a/kernel/user.c b/kernel/user.c
index 7aef4e679a6a0c..21bafdc1137956 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

3/2 2026/07/10 20:32 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16622
							output: 28
							thoughts: 572
						

Thoughts:
**My Fuzzing Strategy for the binfmt_misc Patch**

Okay, so I've got this substantial patch touching `fs/binfmt_misc.c`. It's a big internal refactor, switching from a `list_head` to an `hlist_head`, RCU for lookups, `spinlock_t` in place of `rwlock_t`, cleanup routines, `seq_file` for `bm_entry_read`, and enabling `unlink(2)` on entry files. My immediate concern is, is this reachable, and how can I break it?

First, I need to confirm that this is a critical code path. Yes, `binfmt_misc` is absolutely essential. It's the core of executing arbitrary binary formats within the kernel, so it's a ripe target for fuzzing. I can reach it from userspace by mounting the `binfmt_misc` filesystem and then writing to its files, and it ultimately modifies `load_misc_binary`, which is called during the `execve` system call, so there's plenty of user interaction. That's good; user interaction is critical.

Now, I'm going to map out the attack surface. Based on the patch, it looks like these are the key functions I should be paying attention to: `entry_matches_magic`, `entry_matches_extension`, `search_binfmt_handler`, `get_binfmt_handler`, `put_binfmt_handler`, `current_binfmt_misc`, `load_misc_binary`, `scanarg`, `check_special_flags`, `parse_magic_fields`, `parse_extension_fields`, `create_entry`, `parse_command`, `bm_seq_hex`, `bm_entry_show`, `bm_get_inode`, `bm_evict_inode`, `unlink_binfmt_handler`, `remove_binfmt_handler`, `bm_remove_entry`, `bm_remove_all_entries`, `bm_unlink`, `bm_entry_open`, `bm_entry_write`, `add_entry`, `bm_register_write`, `bm_status_read`, `bm_status_write`, and `bm_fill_super`.

Given that massive list, I need to prioritize. I'm thinking the real meat is in how it's *used*, rather than the internal mechanics. Therefore, I'm zeroing in on: `load_misc_binary` (the ultimate entry point during execution), `bm_register_write`, `bm_entry_write`, `bm_status_write`, `bm_unlink`, `bm_entry_open`, and `bm_entry_show`. Those are the entry points to play with file operations.

Lastly, I need to make sure the build is configured correctly. `CONFIG_BINFMT_MISC` is the relevant Kconfig option. I'll verify this to make sure my testing setup is valid.



4/2 2026/07/10 20:32 tool grepper
0m Args:
{
  "Expression": "BINFMT_MISC",
  "PathPrefix": "fs/"
}

Results:
{
  "Output": "fs/Kconfig.binfmt=138=config BINFMT_ZFLAT\n--\nfs/Kconfig.binfmt-144-\nfs/Kconfig.binfmt:145:config BINFMT_MISC\nfs/Kconfig.binfmt-146-\ttristate \"Kernel support for MISC binaries\"\n--\nfs/Makefile=34=obj-$(CONFIG_FILE_LOCKING)      += locks.o\nfs/Makefile:35:obj-$(CONFIG_BINFMT_MISC)\t+= binfmt_misc.o\nfs/Makefile-36-obj-$(CONFIG_BINFMT_SCRIPT)\t+= binfmt_script.o\n--\nfs/file_table.c=165=static int __init init_fs_stat_sysctls(void)\n--\nfs/file_table.c-167-\tregister_sysctl_init(\"fs\", fs_stat_sysctls);\nfs/file_table.c:168:\tif (IS_ENABLED(CONFIG_BINFMT_MISC)) {\nfs/file_table.c-169-\t\tstruct ctl_table_header *hdr;\n"
}

5/2 2026/07/10 20:32 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18150
							output: 164
							thoughts: 671
						

Thoughts:
**Refining the Focus for BINFMT_MISC Coverage**

Okay, so I'm focusing on `BINFMT_MISC`, that's the key config here. Let's sharpen the list of FocusSymbols for the fuzzer. My initial thoughts were good, `load_misc_binary` is definitely in scope, it's the `linux_binfmt` handler, and critical for execution. The `bm_register_write`, `bm_entry_write`, `bm_status_write`, and `bm_unlink` functions are the handlers for file operations on the virtual filesystem, so they are essential. `bm_entry_open` seems important, and I need to note that it has been changed to `bm_entry_show` now that it uses seq_file.

Now, `load_misc_binary` does get called by `search_binary_handler`, which is itself called by `bprm_execve`. That's a hot path, especially considering the frequency of `execve` calls, and it is specific to `binfmt_misc`, so including it is essential. The others seem like clear wins.

Alright, I can confirm that `BINFMT_MISC` is sufficient for the configuration side of things.

Let's do a final review of the patch. The new `bm_unlink` is a new feature for unlinking entry files, which needs testing. It changes `bm_entry_read` to `bm_entry_show`, which is the seq_file, and important. It also updates `load_misc_binary` to use RCU. Lastly, it updates `create_entry` to use `parse_magic_fields` and `parse_extension_fields`.

Let's refine the list. I have `load_misc_binary`, `bm_register_write`, `bm_entry_write`, `bm_status_write`, `bm_unlink`, and `bm_entry_show`.

I had `create_entry` listed, but that's static. But syzkaller can reach static functions if reachable. But, considering FocusSymbols is about coverage guidance and corpus prioritization, it is more important to list the relevant syscall entry points and the top-level handlers and that's covered in the list above. `bm_register_write` calls `create_entry`.

So the final list for the fuzzer:
*   `load_misc_binary`
*   `bm_register_write`
*   `bm_entry_write`
*   `bm_status_write`
*   `bm_unlink`
*   `bm_entry_show`

The reasoning here is that the patch fundamentally refactors the `binfmt_misc` filesystem with RCU-based lookups, spinlock based locking, `seq_file` for entry reading, and adds entry unlinking, which are all functional changes to important kernel logic that should be fuzzed to prevent regressions, memory leaks, or concurrency problems. Time to set these results and push forward.



6/2 2026/07/10 20:33 tool set-results
0m Args:
{
  "EnableConfigs": [
    "BINFMT_MISC"
  ],
  "FocusSymbols": [
    "load_misc_binary",
    "bm_register_write",
    "bm_entry_write",
    "bm_status_write",
    "bm_unlink",
    "bm_entry_show"
  ],
  "Reasoning": "The patch significantly refactors the binfmt_misc filesystem, introducing RCU-based lookups, changing internal locking from rwlock to spinlock, converting entry reading to use seq_file, and adding support for removing entries via unlink(2). These are functional changes to reachable core kernel logic that should be fuzzed to ensure no regressions, memory leaks, or concurrency issues were introduced.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "BINFMT_MISC"
  ],
  "FocusSymbols": [
    "load_misc_binary",
    "bm_register_write",
    "bm_entry_write",
    "bm_status_write",
    "bm_unlink",
    "bm_entry_show"
  ],
  "Reasoning": "The patch significantly refactors the binfmt_misc filesystem, introducing RCU-based lookups, changing internal locking from rwlock to spinlock, converting entry reading to use seq_file, and adding support for removing entries via unlink(2). These are functional changes to reachable core kernel logic that should be fuzzed to ensure no regressions, memory leaks, or concurrency issues were introduced.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)