From: Christian Brauner <brauner@kernel.org>
To: linux-fsdevel@vger.kernel.org
Cc: Alexander Viro <viro@zeniv.linux.org.uk>,
Christian Brauner <brauner@kernel.org>, Jan Kara <jack@suse.cz>,
linux-mm@kvack.org, Farid Zakaria <farid.m.zakaria@gmail.com>,
jannh@google.com
Subject: [PATCH v3 20/24] binfmt_misc: split the field parsing out of create_entry()
Date: Fri, 10 Jul 2026 11:33:21 +0200 [thread overview]
Message-ID: <20260710-work-binfmt_misc-locking-v3-20-a162f7cb58d6@kernel.org> (raw)
In-Reply-To: <20260710-work-binfmt_misc-locking-v3-0-a162f7cb58d6@kernel.org>
create_entry() is a two hundred line parser with the M and E field
handling inlined as the two arms of its largest branch. Move them
into parse_magic_fields() and parse_extension_fields() which return
the new parse position or NULL so create_entry() itself reads like
the register string grammar again.
The offset parsing loses a provably dead check on the way: after
*s = '\0' and p = s the subsequent if (*p++) always reads the just
written NUL byte and can never fail, it only obscured that the code
simply advances past the delimiter.
With the field parsing gone every remaining failure unwinds the same
way, so hand the entry to __free(kfree), return errors directly and
pass ownership out via no_free_ptr() on success instead of routing
every exit through goto tails.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
fs/binfmt_misc.c | 225 ++++++++++++++++++++++++++-----------------------------
1 file changed, 108 insertions(+), 117 deletions(-)
diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c
index 4cfd3ce5e47e..a9e1e0bbcc60 100644
--- a/fs/binfmt_misc.c
+++ b/fs/binfmt_misc.c
@@ -340,6 +340,95 @@ static char *check_special_flags(char *p, struct binfmt_misc_entry *e)
}
}
+/* 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;
+}
+
/*
* This registers a new binary format, it recognises the syntax
* ':name:type:offset:magic:mask:interpreter:flags'
@@ -348,29 +437,26 @@ static char *check_special_flags(char *p, struct binfmt_misc_entry *e)
static struct binfmt_misc_entry *create_entry(const char __user *buffer,
size_t count)
{
- struct binfmt_misc_entry *e;
+ struct binfmt_misc_entry *e __free(kfree) = NULL;
char *buf, *p;
char del;
- int err;
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;
e = kmalloc(struct_size(e, buf, count + MISC_DELIM_PAD),
GFP_KERNEL_ACCOUNT);
if (!e)
- goto out;
+ return ERR_PTR(-ENOMEM);
p = buf = e->buf;
memset(e, 0, sizeof(*e));
if (copy_from_user(buf, buffer, count))
- goto efault;
+ return ERR_PTR(-EFAULT);
del = *p++; /* delimeter */
@@ -378,7 +464,7 @@ static struct binfmt_misc_entry *create_entry(const char __user *buffer,
/* A flag-char delimiter runs the flag scan off the buffer. */
if (del == 'P' || del == 'O' || del == 'C' || del == 'F')
- goto einval;
+ return ERR_PTR(-EINVAL);
/* Pad the buffer with the delim to simplify parsing below. */
memset(buf + count, del, MISC_DELIM_PAD);
@@ -387,13 +473,13 @@ static struct binfmt_misc_entry *create_entry(const char __user *buffer,
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);
@@ -408,111 +494,26 @@ static struct binfmt_misc_entry *create_entry(const char __user *buffer,
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(MISC_FMT_MAGIC_BIT, &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;
- 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)
- goto einval;
- 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);
- }
+ 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);
- 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);
- } 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. */
@@ -520,19 +521,9 @@ static struct binfmt_misc_entry *create_entry(const char __user *buffer,
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. */
--
2.53.0
next prev parent reply other threads:[~2026-07-10 9:33 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-10 9:33 [PATCH v3 00/24] binfmt_misc: write access fixes, RCU handler lookup and cleanups Christian Brauner
2026-07-10 9:33 ` [PATCH v3 01/24] binfmt_misc: restore write access when removing an entry Christian Brauner
2026-07-10 9:33 ` [PATCH v3 02/24] binfmt_misc: use exe_file_deny_write_access() for the interpreter clone Christian Brauner
2026-07-10 9:33 ` [PATCH v3 03/24] binfmt_misc: reject a flag character as the field delimiter Christian Brauner
2026-07-10 9:33 ` [PATCH v3 04/24] binfmt_misc: convert entry list to an hlist Christian Brauner
2026-07-10 9:33 ` [PATCH v3 05/24] binfmt_misc: use RCU for the handler lookup Christian Brauner
2026-07-10 9:33 ` [PATCH v3 06/24] binfmt_misc: annotate racy accesses to ->enabled Christian Brauner
2026-07-10 9:33 ` [PATCH v3 07/24] binfmt_misc: turn the entry bit numbers into a proper enum Christian Brauner
2026-07-10 9:33 ` [PATCH v3 08/24] binfmt_misc: turn the entry behavior flags into an enum Christian Brauner
2026-07-10 9:33 ` [PATCH v3 09/24] binfmt_misc: rename Node to struct binfmt_misc_entry Christian Brauner
2026-07-10 9:33 ` [PATCH v3 10/24] binfmt_misc: remove the VERBOSE_STATUS toggle Christian Brauner
2026-07-10 9:33 ` [PATCH v3 11/24] binfmt_misc: use print_hex_dump_debug() for the register debug output Christian Brauner
2026-07-10 9:33 ` [PATCH v3 12/24] binfmt_misc: convert the entry file to seq_file Christian Brauner
2026-07-10 9:33 ` [PATCH v3 13/24] binfmt_misc: factor out the entry matching Christian Brauner
2026-07-10 9:33 ` [PATCH v3 14/24] binfmt_misc: rename load_binfmt_misc() to current_binfmt_misc() Christian Brauner
2026-07-10 9:33 ` [PATCH v3 15/24] binfmt_misc: return errors directly in load_misc_binary() Christian Brauner
2026-07-10 9:33 ` [PATCH v3 16/24] binfmt_misc: give the parse_command() results names Christian Brauner
2026-07-10 9:33 ` [PATCH v3 17/24] binfmt_misc: factor out the entry removal Christian Brauner
2026-07-10 9:33 ` [PATCH v3 18/24] binfmt_misc: simplify check_special_flags() Christian Brauner
2026-07-10 9:33 ` [PATCH v3 19/24] binfmt_misc: use a flexible array member for the register string Christian Brauner
2026-07-10 9:33 ` Christian Brauner [this message]
2026-07-10 9:33 ` [PATCH v3 21/24] binfmt_misc: use __free(kfree) in bm_register_write() Christian Brauner
2026-07-10 9:33 ` [PATCH v3 22/24] binfmt_misc: assorted small cleanups Christian Brauner
2026-07-10 9:33 ` [PATCH v3 23/24] binfmt_misc: include what is used Christian Brauner
2026-07-10 9:33 ` [PATCH v3 24/24] binfmt_misc: allow removing entries via unlink(2) Christian Brauner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260710-work-binfmt_misc-locking-v3-20-a162f7cb58d6@kernel.org \
--to=brauner@kernel.org \
--cc=farid.m.zakaria@gmail.com \
--cc=jack@suse.cz \
--cc=jannh@google.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=viro@zeniv.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox