diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c index afd8a737c95b..ad8c4f64bf10 100644 --- a/fs/binfmt_misc.c +++ b/fs/binfmt_misc.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -99,24 +100,6 @@ static const struct binfmt_misc_flag *misc_flag_by_char(const char c) return NULL; } -/** - * struct binfmt_misc_interp - an interpreter an entry was registered with - * @list: link in the entry's list, in registration order - * @file: the file, opened at registration and never resolved again - * @path: the path it was registered under, used as the name the interpreter - * runs under; stored after @name in the same allocation - * @name: the name a load program selects it by; empty for the fixed - * interpreter of a static 'F' entry - * - * Owned by the entry and living exactly as long as it does. - */ -struct binfmt_misc_interp { - struct list_head list; - struct file *file; - const char *path; - char name[]; -}; - struct binfmt_misc_entry { struct hlist_node node; unsigned long flags; /* type, status, etc. */ @@ -252,6 +235,24 @@ static struct binfmt_misc_entry *get_binfmt_handler(struct binfmt_misc *misc, 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) { @@ -261,6 +262,8 @@ static void close_interp_file(struct file *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 @@ -293,7 +296,7 @@ static void entry_put_interpreters(struct binfmt_misc_entry *e) /** * entry_attach_interpreter - bind an opened interpreter to @e * @e: entry being configured - * @name: name a load program can select it by; empty for the fixed + * @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 @@ -301,8 +304,8 @@ static void entry_put_interpreters(struct binfmt_misc_entry *e) * 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 established that @e cannot be matched yet, and - * owns @f until this succeeds. + * 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, a negative errno on failure */ @@ -313,6 +316,11 @@ static int entry_attach_interpreter(struct binfmt_misc_entry *e, size_t nlen = strlen(name), plen = strlen(path); struct binfmt_misc_interp *interp; + if (binfmt_misc_find_interp(&e->interps, name)) + return -EEXIST; + if (list_count_nodes(&e->interps) >= BINFMT_MISC_INTERP_MAX) + return -ENOSPC; + /* One allocation, both strings in it, like the entry's own buffer. */ interp = kmalloc(struct_size(interp, name, nlen + plen + 2), GFP_KERNEL_ACCOUNT); @@ -323,7 +331,8 @@ static int entry_attach_interpreter(struct binfmt_misc_entry *e, strscpy(interp->name, name, nlen + 1); strscpy(interp->name + nlen + 1, path, plen + 1); interp->file = f; - list_add_tail(&interp->list, &e->interps); + /* 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; } @@ -361,6 +370,20 @@ static void put_binfmt_handler(struct binfmt_misc_entry *e) 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 * @@ -394,7 +417,8 @@ static struct binfmt_misc *current_binfmt_misc(void) * @bprm: binary that is being executed * * A static entry carries its interpreter path, for a 'B' entry the - * handler's load program selects it. The match is committed, so a failing + * 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 @@ -404,15 +428,20 @@ static const char *entry_select_interpreter(const struct binfmt_misc_entry *e, { 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; - /* Drop any interpreter or flags a previous chain level staged. */ - kfree(bprm->bpf_interp); - bprm->bpf_interp = NULL; - bprm->bpf_flags = 0; - + /* 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) @@ -430,9 +459,7 @@ static const char *entry_select_interpreter(const struct binfmt_misc_entry *e, drop_staged: /* A failing load leaves nothing behind for later entries. */ - kfree(bprm->bpf_interp_arg); - bprm->bpf_interp_arg = NULL; - bprm->bpf_flags = 0; + drop_staged_selection(bprm); return ERR_PTR(retval); } @@ -477,26 +504,36 @@ static unsigned long entry_invocation_flags(const struct binfmt_misc_entry *e, /** * 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, - * any other entry opens the selected path. + * 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 (!(e->flags & MISC_FMT_OPEN_FILE)) + 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); + } - /* An 'F' entry pre-opened exactly one interpreter. */ - interp = list_first_entry(&e->interps, struct binfmt_misc_interp, list); - interp_file = file_clone_open(interp->file); + interp_file = file_clone_open(bound); if (IS_ERR(interp_file)) return interp_file; @@ -606,7 +643,7 @@ static int load_misc_binary(struct linux_binprm *bprm) * to the real format in the same round. */ if (flags & MISC_FMT_LOADER) { - interp_file = entry_open_interpreter(fmt, interpreter); + 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. */ @@ -628,7 +665,7 @@ static int load_misc_binary(struct linux_binprm *bprm) if (retval < 0) return retval; - interp_file = entry_open_interpreter(fmt, interpreter); + interp_file = entry_open_interpreter(fmt, bprm, interpreter); if (IS_ERR(interp_file)) return PTR_ERR(interp_file); @@ -902,7 +939,7 @@ static struct binfmt_misc_entry *create_entry(const char __user *buffer, /* * A bpf handler decides the invocation flags per exec with * bpf_binprm_set_flags() rather than fixing them at registration, and - * 'F' (pre-open a fixed interpreter) is meaningless for it, so a 'B' + * 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) && @@ -913,7 +950,8 @@ static struct binfmt_misc_entry *create_entry(const char __user *buffer, * '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. - * The first enable publishes it, for good. + * 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; @@ -955,18 +993,17 @@ enum bm_command { 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 __user *buffer, size_t count) +static int parse_command(const char *s, size_t count) { - char s[4]; - - if (count > 3) + if (count > MAX_COMMAND_LENGTH) return -EINVAL; - if (copy_from_user(s, buffer, count)) - return -EFAULT; if (!count) return BM_CMD_IGNORE; if (s[count - 1] == '\n') @@ -980,6 +1017,18 @@ static int parse_command(const char __user *buffer, size_t count) 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. */ @@ -1003,10 +1052,23 @@ static int bm_entry_show(struct seq_file *m, void *unused) else seq_puts(m, "disabled\n"); - if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) + if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) { + struct binfmt_misc_interp *interp; + seq_printf(m, "bpf %s\n", e->bpf_ops->name); - else + /* + * 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: "); @@ -1201,12 +1263,111 @@ static int bm_entry_open(struct inode *inode, struct file *file) return 0; } +/* + * Longest '+ ' 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 '+ ' 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; + + /* '+ ': 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; - int res = parse_command(buffer, count); + 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); + + /* '+ ' 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: @@ -1218,8 +1379,9 @@ static ssize_t bm_entry_write(struct file *file, const char __user *buffer, /* * The first enable publishes a 'D' entry into the search * list, whole. The lock keeps that ordered against a second - * enable and against removal; a removed entry has nothing - * left to publish. + * 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); @@ -1348,7 +1510,7 @@ 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 = parse_command(buffer, count); + int res = read_command(buffer, count); misc = i_binfmt_misc(file_inode(file)); switch (res) { diff --git a/fs/binfmt_misc_bpf.c b/fs/binfmt_misc_bpf.c index 5bf0e46b867c..91576ff05911 100644 --- a/fs/binfmt_misc_bpf.c +++ b/fs/binfmt_misc_bpf.c @@ -7,6 +7,15 @@ * namespace it was registered in. A binfmt_misc 'B' entry activates it: * * echo ':entry:B:::::' > /register + * + * The entry can bind the interpreters the handler may run its binaries + * with, each opened by the write that binds it and selected by name per + * exec. An entry registered with 'D' is not matchable yet, which is what + * leaves it open to being given them: + * + * echo ':entry:B:::::D' > /register + * echo '+ ' > /entry + * echo 1 > /entry */ #include @@ -16,6 +25,8 @@ #include #include #include +#include +#include #include #include #include @@ -88,6 +99,20 @@ bool bpf_prog_is_binfmt_misc_ops(const struct bpf_prog *prog) prog->aux->st_ops == &bpf_binfmt_misc_ops; } +/* + * Replace the staged interpreter selection: naming a path drops a bound + * file, selecting a bound interpreter carries its file along. + */ +static void bm_bpf_stage_selection(struct linux_binprm *bprm, char *path, + struct file *f) +{ + if (bprm->bpf_interp_file) + fput(bprm->bpf_interp_file); + kfree(bprm->bpf_interp); + bprm->bpf_interp = path; + bprm->bpf_interp_file = f; +} + __bpf_kfunc_start_defs(); /** @@ -100,7 +125,8 @@ __bpf_kfunc_start_defs(); * before returning zero; the verifier rejects the call from any other * program, including the handler's own match program. The path is opened * with the credentials of the task doing the exec after the program - * returns. + * returns. Calling it again replaces the selection, as does selecting an + * interpreter the entry bound with bpf_binprm_select_interp(). * * Return: 0 on success, a negative errno on failure */ @@ -124,8 +150,50 @@ __bpf_kfunc int bpf_binprm_set_interp(struct linux_binprm *bprm, if (!interp) return -ENOMEM; - kfree(bprm->bpf_interp); - bprm->bpf_interp = interp; + bm_bpf_stage_selection(bprm, interp, NULL); + return 0; +} + +/** + * bpf_binprm_select_interp - run this exec under an interpreter the entry bound + * @bprm: binary that is being executed + * @name: name the interpreter was registered under + * @name__sz: size of the @name buffer, including the terminating NUL + * + * To be called from the load program of a struct binfmt_misc_ops handler + * instead of bpf_binprm_set_interp(). It selects one of the interpreters + * the matched entry was registered with, each of which was opened once when + * the entry was registered. Nothing is resolved at exec time, so no + * filesystem view can redirect the interpreter. + * + * The interpreter runs under the path the entry registered it under. + * Calling it again replaces the selection. + * + * Return: 0 on success, -ENOENT if the matched entry bound no interpreter + * of that name, a negative errno on failure + */ +__bpf_kfunc int bpf_binprm_select_interp(struct linux_binprm *bprm, + const char *name, size_t name__sz) +{ + const struct binfmt_misc_interp *interp; + size_t len; + char *path; + + if (!name__sz) + return -EINVAL; + len = strnlen(name, name__sz); + if (len == name__sz || !len) + return -EINVAL; + + interp = binfmt_misc_find_interp(bprm->bpf_interps, name); + if (!interp) + return -ENOENT; + + path = kstrdup(interp->path, GFP_KERNEL); + if (!path) + return -ENOMEM; + + bm_bpf_stage_selection(bprm, path, get_file(interp->file)); return 0; } @@ -208,6 +276,7 @@ __bpf_kfunc_end_defs(); BTF_KFUNCS_START(bm_bpf_kfunc_ids) BTF_ID_FLAGS(func, bpf_binprm_set_interp, KF_SLEEPABLE) +BTF_ID_FLAGS(func, bpf_binprm_select_interp, KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_binprm_set_interp_arg, KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_binprm_set_flags, KF_SLEEPABLE) BTF_KFUNCS_END(bm_bpf_kfunc_ids) diff --git a/fs/exec.c b/fs/exec.c index 856731f78d05..a14f28b15607 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1477,6 +1477,8 @@ static void free_bprm(struct linux_binprm *bprm) if (bprm->interp != bprm->filename) kfree(bprm->interp); kfree(bprm->bpf_interp); + if (bprm->bpf_interp_file) + fput(bprm->bpf_interp_file); kfree(bprm->bpf_interp_arg); kfree(bprm->fdpath); kfree(bprm); diff --git a/include/linux/binfmt_misc.h b/include/linux/binfmt_misc.h index 4abdfd36b3fa..072e4b3dd78d 100644 --- a/include/linux/binfmt_misc.h +++ b/include/linux/binfmt_misc.h @@ -5,11 +5,41 @@ #include struct bpf_prog; +struct file; struct linux_binprm; struct user_namespace; #define BINFMT_MISC_OPS_NAME_MAX 16 +/* Longest name a 'B' entry can bind an interpreter under. */ +#define BINFMT_MISC_INTERP_NAME_MAX 32 + +/* Most interpreters one entry can bind. */ +#define BINFMT_MISC_INTERP_MAX 100 + +/** + * struct binfmt_misc_interp - an interpreter an entry was registered with + * @list: link in the entry's list, in registration order + * @file: the file, opened at registration and never resolved again + * @path: the path it was registered under, used as the name the interpreter + * runs under; stored after @name in the same allocation + * @name: the name the load program selects it by; empty for the fixed + * interpreter of a static 'F' entry + * + * Owned by the entry and living exactly as long as it does. The list head + * is handed to the handler's load program for the duration of one exec, + * which picks one with bpf_binprm_select_interp(). + */ +struct binfmt_misc_interp { + struct list_head list; + struct file *file; + const char *path; + char name[]; +}; + +const struct binfmt_misc_interp * +binfmt_misc_find_interp(const struct list_head *interps, const char *name); + /** * enum bpf_binprm_flags - per-exec invocation flags a load program can request * @BPF_BINPRM_PRESERVE_ARGV0: keep the caller's argv[0] (like the 'P' flag) @@ -42,10 +72,11 @@ enum bpf_binprm_flags { * so it can read the binary to decide, but the verifier rejects * the interpreter selection kfuncs in it * @load: select an interpreter for the matched @bprm via - * bpf_binprm_set_interp() and return zero; a match is committed, so - * a failure fails the exec instead of falling through to later - * entries; -ENOEXEC does not fail the exec but moves on to the - * remaining binary formats + * bpf_binprm_set_interp(), or one the entry bound via + * bpf_binprm_select_interp(), and return zero; a match is + * committed, so a failure fails the exec instead of falling + * through to later entries; -ENOEXEC does not fail the exec but + * moves on to the remaining binary formats * @name: name that 'B' entries reference the handler by */ struct binfmt_misc_ops { diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h index a2daecbb01d6..f686a37f7a0a 100644 --- a/include/linux/binfmts.h +++ b/include/linux/binfmts.h @@ -14,7 +14,10 @@ struct coredump_params; /* Interpreter selection staged by a bpf binfmt_misc handler. */ struct binfmt_misc_bpf { + /* interpreters the matched entry bound, selectable by name */ + const struct list_head *bpf_interps; const char *bpf_interp; /* interpreter selected by a bpf handler */ + struct file *bpf_interp_file; /* the bound interpreter it selected */ const char *bpf_interp_arg; /* interpreter argument from a bpf handler */ u64 bpf_flags; /* enum bpf_binprm_flags from a bpf handler */ };