diff options
Diffstat (limited to 'fs/binfmt_misc.c')
| -rw-r--r-- | fs/binfmt_misc.c | 1694 |
1 files changed, 1694 insertions, 0 deletions
diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c new file mode 100644 index 000000000..620da8594 --- /dev/null +++ b/fs/binfmt_misc.c @@ -0,0 +1,1694 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * binfmt_misc.c + * + * Copyright (C) 1997 Richard Günther + * + * binfmt_misc detects binaries via a magic or filename extension and invokes + * a specified wrapper. See Documentation/admin-guide/binfmt-misc.rst for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/array_size.h> +#include <linux/binfmt_misc.h> +#include <linux/binfmts.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/file.h> +#include <linux/fs.h> +#include <linux/fs_context.h> +#include <linux/init.h> +#include <linux/kstrtox.h> +#include <linux/limits.h> +#include <linux/list.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/srcu.h> +#include <linux/string.h> +#include <linux/string_helpers.h> +#include <linux/uaccess.h> +#include <linux/user_namespace.h> + +#include "internal.h" + +/* Entry status and match type bit numbers. */ +enum binfmt_misc_entry_bits { + MISC_FMT_ENABLED_BIT = 0, + MISC_FMT_MAGIC_BIT = 1, + MISC_FMT_BPF_BIT = 2, +}; + +/* 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), + MISC_FMT_TRANSPARENT = (1U << 27), + MISC_FMT_LOADER = (1U << 26), + MISC_FMT_DISABLED = (1U << 25), +}; + +/* The flags that shape the invocation; a 'B' handler picks those per exec. */ +#define MISC_FMT_INVOCATION_FLAGS (MISC_FMT_PRESERVE_ARGV0 | \ + MISC_FMT_OPEN_BINARY | \ + MISC_FMT_CREDENTIALS | \ + MISC_FMT_OPEN_FILE | \ + MISC_FMT_TRANSPARENT | \ + MISC_FMT_LOADER) + +/** + * struct binfmt_misc_flag - a flag character of the register string + * @c: the character userspace writes and reads back + * @flag: the entry flag it sets + * @implies: entry flags it turns on in addition + * @desc: what it does, for the registration debug output + */ +struct binfmt_misc_flag { + char c; + unsigned long flag; + unsigned long implies; + const char *desc; +}; + +static const struct binfmt_misc_flag misc_flags[] = { + { 'P', MISC_FMT_PRESERVE_ARGV0, 0, "preserve argv0" }, + { 'O', MISC_FMT_OPEN_BINARY, 0, "open binary" }, + { 'C', MISC_FMT_CREDENTIALS, MISC_FMT_OPEN_BINARY, "credentials from the binary" }, + { 'F', MISC_FMT_OPEN_FILE, 0, "open interpreter file now" }, + { 'T', MISC_FMT_TRANSPARENT, MISC_FMT_OPEN_BINARY, "transparent" }, + { 'L', MISC_FMT_LOADER, 0, "loader substitution" }, + { 'D', MISC_FMT_DISABLED, 0, "register disabled" }, +}; + +/* Look up a flag character, NULL if @c is not one. */ +static const struct binfmt_misc_flag *misc_flag_by_char(const char c) +{ + for (int i = 0; i < ARRAY_SIZE(misc_flags); i++) + if (misc_flags[i].c == c) + return &misc_flags[i]; + return NULL; +} + +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 */ + char *magic; /* magic or filename extension */ + char *mask; /* mask, NULL for exact match */ + const char *interpreter; /* filename of interpreter */ + char *name; + struct dentry *dentry; + const struct binfmt_misc_ops *bpf_ops; /* bpf-backed handler ('B') */ + const char *bpf_ops_name; + struct list_head interps; /* the interpreters it bound */ + refcount_t users; /* sync removal with load_misc_binary() */ + struct rcu_head rcu; + char buf[]; /* register string, fields point in here */ +}; + +/* + * Max length of the register string. Determined by: + * - 7 delimiters + * - name: ~50 bytes + * - type: 1 byte + * - offset: 3 bytes (has to be smaller than BINPRM_BUF_SIZE) + * - magic: 128 bytes (512 in escaped form) + * - mask: 128 bytes (512 in escaped form) + * - interp: ~50 bytes + * - flags: 5 bytes + * Round that up a bit, and then back off to hold the internal data + * (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 + +/* Protects the entry walk in load_misc_binary(), which may sleep in it. */ +DEFINE_STATIC_SRCU_FAST(bm_entries_srcu); + +/* 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 + * @bprm: binary for which we are looking for a handler + * + * Search for a binary type handler for @bprm in the list of registered binary + * type handlers. A 'B' entry's match program decides whether the handler + * applies; it may sleep to read the binary. The matched entry is returned + * with a reference taken while the walk still held it; a dying entry - + * unlinked with its last reference gone - cannot be matched and the walk + * moves on. + * + * The caller must hold the bm_entries_srcu read lock, which allows an + * entry's evaluation to sleep. + * + * Return: referenced binary type list entry on success, NULL on failure + */ +static struct binfmt_misc_entry * +search_binfmt_handler(struct binfmt_misc *misc, struct linux_binprm *bprm) +{ + char *dot = strrchr(bprm->interp, '.'); + const char *ext = dot ? dot + 1 : NULL; + struct binfmt_misc_entry *e; + + /* Walk all the registered handlers. */ + hlist_for_each_entry_rcu(e, &misc->entries, node, + srcu_read_lock_held(&bm_entries_srcu)) { + /* + * Make sure this one is currently enabled. An entry enters + * the list at most once and only whole: its configuration is + * ordered before the rcu insertion that makes it visible + * here. + */ + if (!test_bit(MISC_FMT_ENABLED_BIT, &e->flags)) + continue; + + if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) { + if (!e->bpf_ops->match(bprm)) + continue; + } else if (test_bit(MISC_FMT_MAGIC_BIT, &e->flags)) { + if (!entry_matches_magic(e, bprm)) + continue; + } else { + if (!entry_matches_extension(e, ext)) + continue; + } + + /* A dying entry cannot be matched, walk on. */ + if (refcount_inc_not_zero(&e->users)) + return e; + } + + return NULL; +} + +/** + * get_binfmt_handler - try to find a binary type handler + * @misc: handle to binfmt_misc instance + * @bprm: binary for which we are looking for a handler + * + * Try to find a binfmt handler for the binary type. If one is found it is + * returned with a reference protecting it against removal via + * bm_{entry,status}_write(). + * + * Return: binary type list entry on success, NULL on failure + */ +static struct binfmt_misc_entry *get_binfmt_handler(struct binfmt_misc *misc, + struct linux_binprm *bprm) +{ + guard(srcu_fast)(&bm_entries_srcu); + return search_binfmt_handler(misc, bprm); +} + +/** + * binfmt_misc_find_interp - find a bound interpreter by name + * @interps: the interpreters the matched entry was registered with + * @name: the name to look for + * + * Return: the interpreter on success, NULL if @interps has none by that name + */ +const struct binfmt_misc_interp * +binfmt_misc_find_interp(const struct list_head *interps, const char *name) +{ + struct binfmt_misc_interp *interp; + + list_for_each_entry(interp, interps, list) + if (!strcmp(interp->name, name)) + return interp; + return NULL; +} + +/* Undo the open_exec() a pre-opened interpreter file came from. */ +static void close_interp_file(struct file *f) +{ + if (IS_ERR_OR_NULL(f)) + return; + exe_file_allow_write_access(f); + filp_close(f, NULL); +} + +DEFINE_FREE(close_interp_file, struct file *, close_interp_file(_T)) + +/* + * Open an interpreter @path for execution: now, in the writer's context, + * and - since binfmt_misc mounts can be unprivileged - with @cred, the + * credentials the control file being written was opened with, not the + * writer's own. + */ +static struct file *open_interp_file(const struct cred *cred, const char *path) +{ + struct file *f; + + scoped_with_creds(cred) + f = open_exec(path); + if (IS_ERR(f)) + pr_notice("register: failed to install interpreter %s\n", path); + return f; +} + +/* Release the interpreters an entry was registered with. */ +static void entry_put_interpreters(struct binfmt_misc_entry *e) +{ + struct binfmt_misc_interp *interp, *tmp; + + list_for_each_entry_safe(interp, tmp, &e->interps, list) { + list_del(&interp->list); + close_interp_file(interp->file); + dec_ucount(interp->ucounts, UCOUNT_BINFMT_MISC_INTERPRETERS); + kfree(interp); + } +} + +/** + * entry_attach_interpreter - bind an opened interpreter to @e + * @e: entry being configured + * @name: name the load program will select it by; empty for the fixed + * interpreter of a static entry + * @path: the path @f was opened from + * @f: the interpreter, opened for execution + * + * Every exec runs a clone of @f, so the path decided which file is bound + * and nothing else: it is not resolved again, in any namespace. + * + * The caller has to have validated @name and @path, established that @e + * cannot be matched yet, and owns @f until this succeeds. + * + * Return: 0 on success, -ENOSPC if the entry is full or the binder is out of + * UCOUNT_BINFMT_MISC_INTERPRETERS budget, a negative errno on failure + */ +static int entry_attach_interpreter(struct binfmt_misc_entry *e, + const char *name, const char *path, + struct file *f) +{ + size_t nlen = strlen(name), plen = strlen(path); + struct binfmt_misc_interp *interp; + struct ucounts *ucounts; + + if (binfmt_misc_find_interp(&e->interps, name)) + return -EEXIST; + if (list_count_nodes(&e->interps) >= BINFMT_MISC_INTERP_MAX) + return -ENOSPC; + + /* The binding keeps a file open, so charge it to whoever binds it. */ + ucounts = inc_ucount(current_user_ns(), current_euid(), + UCOUNT_BINFMT_MISC_INTERPRETERS); + if (!ucounts) + return -ENOSPC; + + /* One allocation, both strings in it, like the entry's own buffer. */ + interp = kmalloc_flex(*interp, name, nlen + plen + 2, + GFP_KERNEL_ACCOUNT); + if (!interp) { + dec_ucount(ucounts, UCOUNT_BINFMT_MISC_INTERPRETERS); + return -ENOMEM; + } + + interp->path = interp->name + nlen + 1; + strscpy(interp->name, name, nlen + 1); + strscpy(interp->name + nlen + 1, path, plen + 1); + interp->file = f; + interp->ucounts = ucounts; + /* Publish the node: a lockless cat may be walking the list. */ + list_add_tail_rcu(&interp->list, &e->interps); + pr_debug("register: interpreter: %s {%s}\n", name, path); + return 0; +} + +static void bm_entry_free_rcu(struct rcu_head *rcu) +{ + struct binfmt_misc_entry *e = container_of(rcu, struct binfmt_misc_entry, rcu); + + /* No walker that could sleep in the handler's programs is left. */ + if (e->bpf_ops) + binfmt_misc_put_ops(e->bpf_ops); + kfree(e); +} + +/** + * put_binfmt_handler - put binary handler entry + * @e: entry to put + * + * 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. Also the teardown for a registration that fails + * before add_entry() publishes the entry. + */ +static void put_binfmt_handler(struct binfmt_misc_entry *e) +{ + if (IS_ERR_OR_NULL(e)) + return; + + if (refcount_dec_and_test(&e->users)) { + entry_put_interpreters(e); + /* Walkers may still dereference this entry, even sleeping. */ + call_srcu(&bm_entries_srcu, &e->rcu, bm_entry_free_rcu); + } +} + +DEFINE_FREE(put_binfmt_handler, struct binfmt_misc_entry *, put_binfmt_handler(_T)) + +/* Drop everything a load program staged for this exec. */ +static void drop_staged_selection(struct linux_binprm *bprm) +{ + kfree(bprm->bpf_interp); + bprm->bpf_interp = NULL; + kfree(bprm->bpf_interp_arg); + bprm->bpf_interp_arg = NULL; + if (bprm->bpf_interp_file) { + fput(bprm->bpf_interp_file); + bprm->bpf_interp_file = NULL; + } + bprm->bpf_flags = 0; +} + +/** + * current_binfmt_misc - get the binfmt_misc instance of the caller's user namespace + * + * 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 *current_binfmt_misc(void) +{ + const struct user_namespace *user_ns; + struct binfmt_misc *misc; + + 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; + } + + return &init_binfmt_misc; +} + +/** + * entry_select_interpreter - get the interpreter for the matched @e + * @e: matched binary type handler + * @bprm: binary that is being executed + * + * A static entry carries its interpreter path, for a 'B' entry the + * handler's load program selects it, either by path or by the name of one + * of the interpreters the entry bound. The match is committed, so a failing + * program fails the exec. + * + * Return: the interpreter on success, an ERR_PTR on failure + */ +static const char *entry_select_interpreter(const struct binfmt_misc_entry *e, + struct linux_binprm *bprm) +{ + int retval; + + /* + * Drop what a previous chain level staged before anything can pick it + * up. A static entry stages nothing but consumes a staged file just + * like a 'B' entry does. + */ + drop_staged_selection(bprm); + + if (!test_bit(MISC_FMT_BPF_BIT, &e->flags)) + return e->interpreter; + + /* The interpreters this entry lets the program choose from. */ + bprm->bpf_interps = &e->interps; + retval = e->bpf_ops->load(bprm); + bprm->bpf_interps = NULL; + if (retval) { + /* Keep a program-supplied error within errno range. */ + if (retval > 0 || retval < -MAX_ERRNO) + retval = -ENOEXEC; + goto drop_staged; + } + + /* Selecting an interpreter is part of the contract. */ + if (!bprm->bpf_interp) { + retval = -ENOEXEC; + goto drop_staged; + } + + return bprm->bpf_interp; + +drop_staged: + /* A failing load leaves nothing behind for later entries. */ + drop_staged_selection(bprm); + return ERR_PTR(retval); +} + +/** + * entry_invocation_flags - the invocation flags in effect for this exec + * @e: matched binary type handler + * @bprm: binary that is being executed + * + * A static entry fixes its flags at registration, a 'B' entry's load program + * picks them per exec with bpf_binprm_set_flags(). Translate the latter into + * the former, implications included, so the dispatch has one set to act on. + * + * Return: the invocation flags for this exec + */ +static unsigned long entry_invocation_flags(const struct binfmt_misc_entry *e, + struct linux_binprm *bprm) +{ + unsigned long flags = 0; + u64 bpf_flags; + + if (!test_bit(MISC_FMT_BPF_BIT, &e->flags)) + return e->flags; + + bpf_flags = bprm->bpf_flags; + /* Clear so they can't accumulate into a nested interpreter level. */ + bprm->bpf_flags = 0; + + if (bpf_flags & BPF_BINPRM_PRESERVE_ARGV0) + flags |= MISC_FMT_PRESERVE_ARGV0; + if (bpf_flags & BPF_BINPRM_EXECFD) + flags |= MISC_FMT_OPEN_BINARY; + if (bpf_flags & BPF_BINPRM_CREDENTIALS) + flags |= MISC_FMT_CREDENTIALS | MISC_FMT_OPEN_BINARY; + if (bpf_flags & BPF_BINPRM_TRANSPARENT) + flags |= MISC_FMT_TRANSPARENT | MISC_FMT_OPEN_BINARY; + if (bpf_flags & BPF_BINPRM_LOADER) + flags |= MISC_FMT_LOADER; + + return flags; +} + +/** + * entry_open_interpreter - open the entry's interpreter for execution + * @e: matched binary type handler + * @bprm: binary that is being executed + * @interpreter: the interpreter selected for this exec + * + * An 'F' entry hands out a clone of the file it pre-opened at registration, + * and so does a 'B' entry whose load program selected one of the + * interpreters it bound. Any other entry opens the selected path. + * + * Return: the opened interpreter on success, an ERR_PTR on failure + */ +static struct file *entry_open_interpreter(const struct binfmt_misc_entry *e, + struct linux_binprm *bprm, + const char *interpreter) +{ + struct file *interp_file __free(fput) = NULL; + struct binfmt_misc_interp *interp; + struct file *bound; + int retval; + + if (bprm->bpf_interp_file) { + bound = bprm->bpf_interp_file; + } else if (e->flags & MISC_FMT_OPEN_FILE) { + /* An 'F' entry pre-opened exactly one interpreter. */ + interp = list_first_entry(&e->interps, + struct binfmt_misc_interp, list); + bound = interp->file; + } else { + return open_exec(interpreter); + } + + interp_file = file_clone_open(bound); + if (IS_ERR(interp_file)) + return interp_file; + + retval = exe_file_deny_write_access(interp_file); + if (retval) + return ERR_PTR(retval); + + return no_free_ptr(interp_file); +} + +/** + * build_interp_argv - splice the interpreter invocation into the argv + * @bprm: binary that is being executed + * @interpreter: the interpreter selected for this exec + * @flags: invocation flags in effect for this exec + * + * The interpreter becomes argv[0] and the binary its last argument, with an + * optional staged argument in between. The caller's argv[0] is dropped + * unless 'P' keeps it. + * + * Return: 0 on success, a negative error code on failure + */ +static int build_interp_argv(struct linux_binprm *bprm, const char *interpreter, + unsigned long flags) +{ + int retval; + + /* The interpreter has to be able to load the binary by path. */ + if (bprm->interp_flags & BINPRM_FLAGS_PATH_INACCESSIBLE) + return -ENOENT; + + /* The entry's own choice - not one accumulated from an earlier level. */ + if (flags & MISC_FMT_PRESERVE_ARGV0) { + bprm->interp_flags |= BINPRM_FLAGS_PRESERVE_ARGV0; + } else { + retval = remove_arg_zero(bprm); + if (retval) + return retval; + } + + /* make the binary the last argument to the interpreter */ + retval = copy_string_kernel(bprm->interp, bprm); + if (retval < 0) + return retval; + bprm->argc++; + + /* + * A single optional argument to the interpreter, inserted between it + * and the binary just like the argument of a #! interpreter line. + */ + if (bprm->bpf_interp_arg) { + retval = copy_string_kernel(bprm->bpf_interp_arg, bprm); + if (retval < 0) + return retval; + bprm->argc++; + /* Consumed - don't let it leak into a nested interpreter's argv. */ + kfree(bprm->bpf_interp_arg); + bprm->bpf_interp_arg = NULL; + } + + /* add the interp as argv[0] */ + retval = copy_string_kernel(interpreter, bprm); + if (retval < 0) + return retval; + bprm->argc++; + + return 0; +} + +/* + * the loader itself + */ +static int load_misc_binary(struct linux_binprm *bprm) +{ + struct binfmt_misc_entry *fmt __free(put_binfmt_handler) = NULL; + const char *interpreter; + struct file *interp_file; + struct binfmt_misc *misc; + unsigned long flags; + int retval; + + /* Only binfmt_misc stages one and exec_binprm() clears it per round. */ + WARN_ON_ONCE(bprm->loader); + + misc = current_binfmt_misc(); + if (!READ_ONCE(misc->enabled)) + return -ENOEXEC; + + fmt = get_binfmt_handler(misc, bprm); + if (!fmt) + return -ENOEXEC; + + interpreter = entry_select_interpreter(fmt, bprm); + if (IS_ERR(interpreter)) + return PTR_ERR(interpreter); + + flags = entry_invocation_flags(fmt, bprm); + + /* No argv is built for a staged argument to land in. */ + if ((flags & (MISC_FMT_LOADER | MISC_FMT_TRANSPARENT)) && + bprm->bpf_interp_arg) + return -EINVAL; + + /* + * Stash the interpreter for binfmt_elf to consume in place of the + * binary's PT_INTERP and decline the match, so the search continues + * to the real format in the same round. + */ + if (flags & MISC_FMT_LOADER) { + interp_file = entry_open_interpreter(fmt, bprm, interpreter); + if (IS_ERR(interp_file)) { + retval = PTR_ERR(interp_file); + /* Declining here would run the binary's own PT_INTERP. */ + return retval == -ENOEXEC ? -EACCES : retval; + } + + bprm->loader = interp_file; + return -ENOEXEC; + } + + if (!(flags & MISC_FMT_TRANSPARENT)) { + retval = build_interp_argv(bprm, interpreter, flags); + if (retval) + return retval; + } + + /* Update interp for the next round; sched_prepare_exec reports it. */ + retval = bprm_change_interp(interpreter, bprm); + if (retval < 0) + return retval; + + interp_file = entry_open_interpreter(fmt, bprm, interpreter); + if (IS_ERR(interp_file)) + return PTR_ERR(interp_file); + + /* Raise only past the last failure, or an -ENOEXEC decline leaks it. */ + if (flags & MISC_FMT_TRANSPARENT) + bprm->interp_flags |= BINPRM_FLAGS_TRANSPARENT_INTERP; + + bprm->interpreter = interp_file; + if (flags & MISC_FMT_OPEN_BINARY) + bprm->have_execfd = 1; + if (flags & MISC_FMT_CREDENTIALS) + bprm->execfd_creds = 1; + return 0; +} + +/* Command parsers */ + +/* + * 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) +{ + char c; + + while ((c = *s++) != del) { + if (c == '\\' && *s == 'x') { + s++; + if (!isxdigit(*s++)) + return NULL; + if (!isxdigit(*s++)) + return NULL; + } + } + s[-1] = '\0'; + return s; +} + +/* Parse the 'flags' field, stopping at the first character that is not one. */ +static char *check_special_flags(char *p, struct binfmt_misc_entry *e) +{ + for (;; p++) { + const struct binfmt_misc_flag *f = misc_flag_by_char(*p); + + if (!f) + return p; + pr_debug("register: flag: %c (%s)\n", f->c, f->desc); + e->flags |= f->flag | f->implies; + } +} + +/* 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; +} + +/* + * Parse the fields of a 'B' entry: the 'offset', 'magic' and 'mask' fields + * must be empty. The handler name is carried in the 'interpreter' field. + */ +static char *parse_bpf_fields(struct binfmt_misc_entry *e, char *p, char del) +{ + /* The 'offset' field must be empty. */ + if (*p++ != del) + return NULL; + + /* The 'magic' field must be empty. */ + if (*p++ != del) + return NULL; + + /* The 'mask' field must be empty. */ + if (*p++ != del) + return NULL; + + return p; +} + +/* + * This registers a new binary format, it recognises the syntax + * ':name:type:offset:magic:mask:interpreter:flags' + * where the ':' is the IFS, that can be chosen with the first char + */ +static struct binfmt_misc_entry *create_entry(const char __user *buffer, + size_t count) +{ + struct binfmt_misc_entry *e __free(kfree) = NULL; + char *buf, *p; + char del; + + pr_debug("register: received %zu bytes\n", count); + + /* some sanity checks */ + if ((count < 11) || (count > MAX_REGISTER_LENGTH)) + return ERR_PTR(-EINVAL); + + e = kmalloc_flex(*e, buf, count + MISC_DELIM_PAD, GFP_KERNEL_ACCOUNT); + if (!e) + return ERR_PTR(-ENOMEM); + + p = buf = e->buf; + + memset(e, 0, sizeof(*e)); + INIT_LIST_HEAD(&e->interps); + if (copy_from_user(buf, buffer, count)) + return ERR_PTR(-EFAULT); + + del = *p++; /* delimiter */ + + pr_debug("register: delim: %#x {%c}\n", del, del); + + /* A flag-char delimiter runs the flag scan off the buffer. */ + if (misc_flag_by_char(del)) + return ERR_PTR(-EINVAL); + + /* Pad the buffer with the delim to simplify parsing below. */ + memset(buf + count, del, MISC_DELIM_PAD); + + /* Parse the 'name' field. */ + e->name = p; + p = strchr(p, del); + if (!p) + return ERR_PTR(-EINVAL); + *p++ = '\0'; + if (!e->name[0] || + !strcmp(e->name, ".") || + !strcmp(e->name, "..") || + strchr(e->name, '/')) + return ERR_PTR(-EINVAL); + + pr_debug("register: name: {%s}\n", e->name); + + /* Parse the 'type' field. */ + switch (*p++) { + case 'E': + pr_debug("register: type: E (extension)\n"); + e->flags = BIT(MISC_FMT_ENABLED_BIT); + break; + case 'M': + pr_debug("register: type: M (magic)\n"); + e->flags = BIT(MISC_FMT_ENABLED_BIT) | BIT(MISC_FMT_MAGIC_BIT); + break; + case 'B': + pr_debug("register: type: B (bpf)\n"); + if (!IS_ENABLED(CONFIG_BINFMT_MISC_BPF)) + return ERR_PTR(-EINVAL); + e->flags = BIT(MISC_FMT_ENABLED_BIT) | BIT(MISC_FMT_BPF_BIT); + break; + default: + return ERR_PTR(-EINVAL); + } + if (*p++ != del) + return ERR_PTR(-EINVAL); + + if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) + p = parse_bpf_fields(e, p, del); + else 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) + return ERR_PTR(-EINVAL); + *p++ = '\0'; + if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) { + /* The 'interpreter' field carries the handler name. */ + e->bpf_ops_name = e->interpreter; + e->interpreter = NULL; + if (!e->bpf_ops_name[0]) + return ERR_PTR(-EINVAL); + pr_debug("register: bpf handler: {%s}\n", e->bpf_ops_name); + } else if (!e->interpreter[0]) { + return ERR_PTR(-EINVAL); + } else { + pr_debug("register: interpreter: {%s}\n", e->interpreter); + } + + /* Parse the 'flags' field. */ + p = check_special_flags(p, e); + + /* + * A bpf handler decides the invocation flags per exec with + * bpf_binprm_set_flags() rather than fixing them at registration, and + * the interpreters it binds pre-open what 'F' would have, so a 'B' + * entry carries no invocation flags. + */ + if (test_bit(MISC_FMT_BPF_BIT, &e->flags) && + (e->flags & MISC_FMT_INVOCATION_FLAGS)) + return ERR_PTR(-EINVAL); + + /* + * 'D' is a directive for this registration rather than a lasting + * property, so consume it: the entry is created disabled and stays + * out of the search list until '1' is written to its entry file. + * Staying out is what leaves it open to being given interpreters; + * the first enable publishes it, for good. + */ + if (e->flags & MISC_FMT_DISABLED) { + e->flags &= ~MISC_FMT_DISABLED; + clear_bit(MISC_FMT_ENABLED_BIT, &e->flags); + } + + /* Transparency preserves the whole argv, argv[0] included. */ + if ((e->flags & MISC_FMT_TRANSPARENT) && + (e->flags & MISC_FMT_PRESERVE_ARGV0)) + return ERR_PTR(-EINVAL); + + /* A native exec splices no argv, passes no execfd and needs no creds. */ + if ((e->flags & MISC_FMT_LOADER) && + (e->flags & (MISC_FMT_TRANSPARENT | MISC_FMT_PRESERVE_ARGV0 | + MISC_FMT_CREDENTIALS | MISC_FMT_OPEN_BINARY))) + return ERR_PTR(-EINVAL); + + if (*p == '\n') + p++; + if (p != buf + count) + return ERR_PTR(-EINVAL); + + /* Non-F opens the interp at exec against the caller's cwd; require absolute. */ + if ((e->flags & (MISC_FMT_LOADER | MISC_FMT_CREDENTIALS)) && + !(e->flags & MISC_FMT_OPEN_FILE) && + e->interpreter[0] != '/') + return ERR_PTR(-EINVAL); + + /* Born holding one reference; put_binfmt_handler() is the teardown. */ + refcount_set(&e->users, 1); + 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" */ +}; + +/* Longest of the commands above, "-1\n". */ +#define MAX_COMMAND_LENGTH 3 + +/* + * 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 *s, size_t count) +{ + if (count > MAX_COMMAND_LENGTH) + return -EINVAL; + if (!count) + return BM_CMD_IGNORE; + if (s[count - 1] == '\n') + count--; + if (count == 1 && s[0] == '0') + return BM_CMD_DISABLE; + if (count == 1 && s[0] == '1') + return BM_CMD_ENABLE; + if (count == 2 && s[0] == '-' && s[1] == '1') + return BM_CMD_REMOVE; + return -EINVAL; +} + +/* Copy in a command from a file that takes nothing else, and parse it. */ +static int read_command(const char __user *buffer, size_t count) +{ + char s[MAX_COMMAND_LENGTH + 1]; + + if (count > sizeof(s) - 1) + return -EINVAL; + if (copy_from_user(s, buffer, count)) + return -EFAULT; + return parse_command(s, count); +} + +/* generic stuff */ + +/* The root directory's inode; its lock serializes configuring an instance. */ +static struct inode *bm_root_inode(struct super_block *sb) +{ + return d_inode(sb->s_root); +} + +static void bm_seq_hex(struct seq_file *m, const u8 *data, int size) +{ + for (int i = 0; i < size; i++) + seq_printf(m, "%02x", data[i]); +} + +static int bm_entry_show(struct seq_file *m, void *unused) +{ + struct binfmt_misc_entry *e = m->private; + + if (test_bit(MISC_FMT_ENABLED_BIT, &e->flags)) + seq_puts(m, "enabled\n"); + else + seq_puts(m, "disabled\n"); + + if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) { + struct binfmt_misc_interp *interp; + + seq_printf(m, "bpf %s\n", e->bpf_ops->name); + /* + * A staged entry's set can still grow, so every binding is + * rcu-published. The open file pins the entry and with it + * every node, so rcu is for the tearing, not the lifetime. + */ + rcu_read_lock(); + list_for_each_entry_rcu(interp, &e->interps, list) + seq_printf(m, "bpf-interpreter %s %s\n", + interp->name, interp->path); + rcu_read_unlock(); + } else { + seq_printf(m, "interpreter %s\n", e->interpreter); + } + + /* print the special flags */ + seq_puts(m, "flags: "); + for (int i = 0; i < ARRAY_SIZE(misc_flags); i++) + if (e->flags & misc_flags[i].flag) + seq_putc(m, misc_flags[i].c); + seq_putc(m, '\n'); + + if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) { + /* The program does the matching. */ + } else if (!test_bit(MISC_FMT_MAGIC_BIT, &e->flags)) { + seq_printf(m, "extension .%s\n", e->magic); + } else { + seq_printf(m, "offset %i\nmagic ", e->offset); + bm_seq_hex(m, e->magic, e->size); + if (e->mask) { + seq_puts(m, "\nmask "); + bm_seq_hex(m, e->mask, e->size); + } + seq_putc(m, '\n'); + } + return 0; +} + +static struct inode *bm_get_inode(struct super_block *sb, umode_t mode) +{ + struct inode *inode = new_inode(sb); + + if (inode) { + inode->i_ino = get_next_ino(); + inode->i_mode = mode; + simple_inode_init_ts(inode); + } + return inode; +} + +/** + * i_binfmt_misc - retrieve struct binfmt_misc from a binfmt_misc inode + * @inode: inode of the relevant binfmt_misc instance + * + * This helper retrieves struct binfmt_misc from a binfmt_misc inode. This can + * be done without any memory barriers because we are guaranteed that + * user_ns->binfmt_misc is fully initialized. It was fully initialized when the + * binfmt_misc mount was first created. + * + * Return: struct binfmt_misc of the relevant binfmt_misc instance + */ +static struct binfmt_misc *i_binfmt_misc(struct inode *inode) +{ + return inode->i_sb->s_user_ns->binfmt_misc; +} + +/** + * bm_evict_inode - cleanup data associated with @inode + * @inode: inode to which the data is attached + * + * Cleanup the binary type handler data associated with @inode if a binary type + * 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 + * 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) +{ + struct binfmt_misc_entry *e = inode->i_private; + + clear_inode(inode); + + if (e) { + struct binfmt_misc *misc; + + misc = i_binfmt_misc(inode); + 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 SRCU. 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. + */ +static void remove_binfmt_handler(struct binfmt_misc *misc, + struct binfmt_misc_entry *e) +{ + unlink_binfmt_handler(misc, e); + locked_recursive_removal(e->dentry, NULL); +} + +/* Remove @e unless it was already removed. */ +static void bm_remove_entry(struct binfmt_misc_entry *e, struct super_block *sb) +{ + struct inode *root = bm_root_inode(sb); + + inode_lock_nested(root, I_MUTEX_PARENT); + /* A staged entry is not hashed; the dentry says if it was removed. */ + if (!d_unhashed(e->dentry)) + 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) +{ + struct inode *root = bm_root_inode(sb); + struct dentry *child = NULL; + + inode_lock_nested(root, I_MUTEX_PARENT); + /* + * Walk the directory rather than the search list: a staged entry + * is in the former but not yet in the latter. The control files + * carry no entry and stay. + */ + while ((child = find_next_child(sb->s_root, child))) { + struct binfmt_misc_entry *e = d_inode(child)->i_private; + + if (e) + remove_binfmt_handler(misc, e); + } + inode_unlock(root); +} + +/** + * 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); +} + +static const struct inode_operations bm_dir_inode_operations = { + .lookup = simple_lookup, + .unlink = bm_unlink, +}; + +/* /<entry> */ + +static int bm_entry_open(struct inode *inode, struct file *file) +{ + int ret; + + 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; +} + +/* + * Longest '+<name> <path>' a write can spell, and with it the longest + * command an entry file takes: the two delimiters and a newline on top of + * the two names. + */ +#define MAX_BINDING_LENGTH (BINFMT_MISC_INTERP_NAME_MAX + PATH_MAX + 3) + +/** + * bm_entry_add_interp - bind another interpreter to a staged entry + * @e: the entry + * @file: the entry file being written to, for its credentials + * @buf: the '+<name> <path>' command, parsed in place and owned by the caller + * @count: its length + * + * A 'D' entry is registered outside the search list, which is what leaves + * it open to being configured: it cannot be matched, so no exec can be + * holding its interpreters and the set can still grow. Its first enable + * publishes it and ends that. One interpreter per write, up to + * BINFMT_MISC_INTERP_MAX of them, none of which has to fit in a register + * string. + * + * Return: @count on success, a negative errno on failure + */ +static ssize_t bm_entry_add_interp(struct binfmt_misc_entry *e, + struct file *file, char *buf, size_t count) +{ + struct file *f __free(close_interp_file) = NULL; + struct inode *root = bm_root_inode(file_inode(file)->i_sb); + size_t nlen, plen; + char *name, *path; + int retval; + + /* Settled before the open: type is fixed, publication is permanent. */ + if (!test_bit(MISC_FMT_BPF_BIT, &e->flags)) + return -EINVAL; + if (!hlist_unhashed_lockless(&e->node)) + return -EBUSY; + + /* '+<name> <path>': the path is everything past the first space. */ + name = buf + 1; + path = strchr(name, ' '); + if (!path) + return -EINVAL; + *path++ = '\0'; + + plen = strlen(path); + /* The command has to end at the write, like a register string. */ + if (path + plen != buf + count) + return -EINVAL; + if (plen && path[plen - 1] == '\n') + path[--plen] = '\0'; + /* Resolved now, so a relative path would name the writer's cwd. */ + if (path[0] != '/') + return -EINVAL; + + nlen = path - name - 1; + if (!nlen || nlen > BINFMT_MISC_INTERP_NAME_MAX) + return -EINVAL; + /* The name prints between delimiters, so keep it a printable word. */ + for (const char *p = name; *p; p++) + if (!isascii(*p) || !isgraph(*p)) + return -EINVAL; + + /* Opened before the lock: resolving it may walk this very filesystem. */ + f = open_interp_file(file->f_cred, path); + if (IS_ERR(f)) + return PTR_ERR(f); + + inode_lock(root); + if (d_unhashed(e->dentry)) + retval = -ENOENT; /* removed while we were opening it */ + else if (!hlist_unhashed(&e->node)) + retval = -EBUSY; /* published while we were opening it */ + else + retval = entry_attach_interpreter(e, name, path, f); + inode_unlock(root); + if (retval) + return retval; + + /* The file is owned by the entry now. */ + retain_and_null_ptr(f); + return count; +} + +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); + struct binfmt_misc_entry *e = inode->i_private; + char *buf __free(kfree) = NULL; + int res; + + /* A binding is the longest command this file takes. */ + if (count > MAX_BINDING_LENGTH) + return -E2BIG; + + buf = memdup_user_nul(buffer, count); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + /* '+<name> <path>' binds an interpreter, everything else toggles. */ + if (buf[0] == '+') + return bm_entry_add_interp(e, file, buf, count); + + res = parse_command(buf, count); + + switch (res) { + case BM_CMD_DISABLE: + clear_bit(MISC_FMT_ENABLED_BIT, &e->flags); + break; + case BM_CMD_ENABLE: { + struct inode *root = bm_root_inode(inode->i_sb); + + /* + * The first enable publishes a 'D' entry into the search + * list, whole. The lock keeps that ordered against a second + * enable, against removal - a removed entry has nothing left + * to publish - and against binding: what can be matched can + * no longer be configured. + */ + inode_lock(root); + set_bit(MISC_FMT_ENABLED_BIT, &e->flags); + if (hlist_unhashed(&e->node) && !d_unhashed(e->dentry)) { + struct binfmt_misc *misc = i_binfmt_misc(inode); + + spin_lock(&misc->entries_lock); + hlist_add_head_rcu(&e->node, &misc->entries); + spin_unlock(&misc->entries_lock); + } + inode_unlock(root); + break; + } + case BM_CMD_REMOVE: + bm_remove_entry(e, inode->i_sb); + break; + default: + return res; + } + + return count; +} + +static const struct file_operations bm_entry_operations = { + .open = bm_entry_open, + .read = seq_read, + .write = bm_entry_write, + .llseek = seq_lseek, + .release = single_release, +}; + +/* /register */ + +/* add to filesystem */ +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; + struct binfmt_misc *misc; + + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + + inode = bm_get_inode(sb, S_IFREG | 0644); + if (unlikely(!inode)) { + simple_done_creating(dentry); + return -ENOMEM; + } + + e->dentry = dentry; + inode->i_private = e; + inode->i_fop = &bm_entry_operations; + + d_make_persistent(dentry, inode); + /* A 'D' entry stays out of the search list until its first enable. */ + if (test_bit(MISC_FMT_ENABLED_BIT, &e->flags)) { + misc = i_binfmt_misc(inode); + spin_lock(&misc->entries_lock); + hlist_add_head_rcu(&e->node, &misc->entries); + spin_unlock(&misc->entries_lock); + } + simple_done_creating(dentry); + return 0; +} + +static ssize_t bm_register_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct binfmt_misc_entry *e __free(put_binfmt_handler) = NULL; + struct super_block *sb = file_inode(file)->i_sb; + int err; + + e = create_entry(buffer, count); + if (IS_ERR(e)) + return PTR_ERR(e); + + if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) { + e->bpf_ops = binfmt_misc_get_ops(sb->s_user_ns, e->bpf_ops_name); + if (!e->bpf_ops) { + pr_notice("register: no bpf handler named %s\n", + e->bpf_ops_name); + return -ENOENT; + } + } + + if (e->flags & MISC_FMT_OPEN_FILE) { + struct file *f = open_interp_file(file->f_cred, e->interpreter); + + if (IS_ERR(f)) + return PTR_ERR(f); + err = entry_attach_interpreter(e, "", e->interpreter, f); + if (err) { + close_interp_file(f); + return err; + } + } + + err = add_entry(e, sb); + if (err) + return err; + + /* The entry is owned by its inode now. */ + retain_and_null_ptr(e); + return count; +} + +static const struct file_operations bm_register_operations = { + .write = bm_register_write, + .llseek = noop_llseek, +}; + +/* /status */ + +static ssize_t +bm_status_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) +{ + struct binfmt_misc *misc; + const char *s; + + misc = i_binfmt_misc(file_inode(file)); + s = READ_ONCE(misc->enabled) ? "enabled\n" : "disabled\n"; + return simple_read_from_buffer(buf, nbytes, ppos, s, strlen(s)); +} + +static ssize_t bm_status_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct binfmt_misc *misc; + int res = read_command(buffer, count); + + misc = i_binfmt_misc(file_inode(file)); + switch (res) { + case BM_CMD_DISABLE: + WRITE_ONCE(misc->enabled, false); + break; + case BM_CMD_ENABLE: + WRITE_ONCE(misc->enabled, true); + break; + case BM_CMD_REMOVE: + bm_remove_all_entries(misc, file_inode(file)->i_sb); + break; + default: + return res; + } + + return count; +} + +static const struct file_operations bm_status_operations = { + .read = bm_status_read, + .write = bm_status_write, + .llseek = default_llseek, +}; + +/* Superblock handling */ + +static const struct super_operations bm_super_ops = { + .statfs = simple_statfs, + .evict_inode = bm_evict_inode, +}; + +static int bm_fill_super(struct super_block *sb, struct fs_context *fc) +{ + int err; + struct user_namespace *user_ns = sb->s_user_ns; + struct binfmt_misc *misc; + static const struct tree_descr bm_files[] = { + [2] = {"status", &bm_status_operations, S_IWUSR|S_IRUGO}, + [3] = {"register", &bm_register_operations, S_IWUSR}, + /* last one */ {""} + }; + + /* The fscontext fd may have been passed to another user namespace. */ + if (user_ns != current_user_ns()) + return -EINVAL; + + /* Never exec off this instance and never let anything stack on it. */ + sb->s_iflags |= SB_I_NOEXEC | SB_I_NODEV; + sb->s_stack_depth = FILESYSTEM_MAX_STACK_DEPTH; + + /* + * Lazily allocate a new binfmt_misc instance for this namespace, i.e. + * do it here during the first mount of binfmt_misc. We don't need to + * waste memory for every user namespace allocation. It's likely much + * more common to not mount a separate binfmt_misc instance than it is + * to mount one. + * + * While multiple superblocks can exist they are keyed by userns in + * s_fs_info for binfmt_misc. Hence, the vfs guarantees that + * bm_fill_super() is called exactly once whenever a binfmt_misc + * superblock for a userns is created. This in turn lets us conclude + * that when a binfmt_misc superblock is created for the first time for + * a userns there's no one racing us. Therefore we don't need any + * barriers when we dereference binfmt_misc. + */ + misc = user_ns->binfmt_misc; + if (!misc) { + /* + * If it turns out that most user namespaces actually want to + * register their own binary type handler and therefore all + * create their own separate binfmt_misc mounts we should + * consider turning this into a kmem cache. + */ + misc = kzalloc_obj(struct binfmt_misc); + if (!misc) + return -ENOMEM; + + INIT_HLIST_HEAD(&misc->entries); + spin_lock_init(&misc->entries_lock); + + /* Pairs with smp_load_acquire() in current_binfmt_misc(). */ + smp_store_release(&user_ns->binfmt_misc, misc); + } + + /* + * When the binfmt_misc superblock for this userns is shutdown + * ->enabled might have been set to false and we don't reinitialize + * ->enabled again during shutdown as someone might already be mounting + * binfmt_misc again. It also would be pointless since by then we know + * that the binary type list for this binfmt_misc mount is empty making + * load_misc_binary() return -ENOEXEC independent of whether ->enabled + * is true. Instead, if someone mounts binfmt_misc for the first time or + * again we simply reset ->enabled to true. + */ + WRITE_ONCE(misc->enabled, true); + + err = simple_fill_super(sb, BINFMTFS_MAGIC, bm_files); + 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) +{ + if (fc->s_fs_info) + put_user_ns(fc->s_fs_info); +} + +static int bm_get_tree(struct fs_context *fc) +{ + return get_tree_keyed(fc, bm_fill_super, get_user_ns(fc->user_ns)); +} + +static const struct fs_context_operations bm_context_ops = { + .free = bm_free, + .get_tree = bm_get_tree, +}; + +static void bm_kill_sb(struct super_block *sb) +{ + struct user_namespace *user_ns = sb->s_fs_info; + + kill_anon_super(sb); + put_user_ns(user_ns); +} + +static int bm_init_fs_context(struct fs_context *fc) +{ + fc->ops = &bm_context_ops; + return 0; +} + +static struct linux_binfmt misc_format = { + .module = THIS_MODULE, + .load_binary = load_misc_binary, +}; + +static struct file_system_type bm_fs_type = { + .owner = THIS_MODULE, + .name = "binfmt_misc", + .init_fs_context = bm_init_fs_context, + .fs_flags = FS_USERNS_MOUNT, + .kill_sb = bm_kill_sb, +}; +MODULE_ALIAS_FS("binfmt_misc"); + +static int __init init_misc_binfmt(void) +{ + int err = register_filesystem(&bm_fs_type); + if (!err) + insert_binfmt(&misc_format); + return err; +} + +static void __exit exit_misc_binfmt(void) +{ + unregister_binfmt(&misc_format); + unregister_filesystem(&bm_fs_type); + /* Flush pending bm_entry_free_rcu() callbacks before the text goes. */ + srcu_barrier(&bm_entries_srcu); +} + +core_initcall(init_misc_binfmt); +module_exit(exit_misc_binfmt); +MODULE_DESCRIPTION("Kernel support for miscellaneous binaries"); +MODULE_LICENSE("GPL"); |
