A binfmt_misc interpreter is visible to the binary it runs: argv[0] becomes the interpreter path, the binary's path is appended as an argument and /proc/pid/cmdline shows both. For wine or qemu-user that is the point, but for a per-binary loader the interpreter is an implementation detail of running the binary that has no business in the argument vector. And a binary handed to execveat() as an O_CLOEXEC fd without a usable path cannot be run through binfmt_misc at all: the interpreter would have no path to open the binary by, so load_misc_binary() refuses upfront with -ENOENT. Add BPF_BINPRM_TRANSPARENT. It goes beyond the static flags: the binary is handed to the interpreter through AT_EXECFD as with BPF_BINPRM_EXECFD, but the argument vector and bprm->interp are also left exactly as the caller set them, so argv[0] and /proc/pid/cmdline look like a direct execution of the binary. The interpreter has to load the binary from AT_EXECFD for this; a relocatable loader can, glibc's ld.so does not. The inaccessible-path bail moves after the load program has run and into the path-building branch: a transparent interpreter takes the binary from AT_EXECFD instead of a path, so the restriction no longer applies and the O_CLOEXEC execveat() case above just works. BPF_BINPRM_PRESERVE_ARGV0 is subsumed - transparency preserves the whole argument vector, argv[0] included - and a staged interpreter argument is dropped: no argv slot is built for it to land in. Signed-off-by: Christian Brauner (Amutable) --- Documentation/admin-guide/binfmt-misc.rst | 9 +++ fs/binfmt_misc.c | 92 ++++++++++++++++++------------- fs/binfmt_misc_bpf.c | 6 +- include/linux/binfmt_misc.h | 3 + 4 files changed, 71 insertions(+), 39 deletions(-) diff --git a/Documentation/admin-guide/binfmt-misc.rst b/Documentation/admin-guide/binfmt-misc.rst index 7f42abf9cfba..b446b3be9628 100644 --- a/Documentation/admin-guide/binfmt-misc.rst +++ b/Documentation/admin-guide/binfmt-misc.rst @@ -148,6 +148,15 @@ decide them differently for each binary it handles: - ``BPF_BINPRM_EXECFD`` opens the binary on the interpreter's behalf and passes it through the ``AT_EXECFD`` aux vector entry (the ``O`` flag), so the interpreter can run binaries it could not open by path. +- ``BPF_BINPRM_TRANSPARENT`` runs the interpreter transparently. It has no + static counterpart: the binary is handed over through ``AT_EXECFD`` as + with ``BPF_BINPRM_EXECFD``, but the argument vector is also left as the + caller passed it. An interpreter that loads the binary from ``AT_EXECFD`` + then appears in ``argv[0]`` and ``/proc/pid/cmdline`` as a direct + execution of the binary. This is also the only way a binfmt_misc handler + can run a binary passed as an inaccessible ``O_CLOEXEC`` file descriptor + to ``execveat()``, which otherwise fails because the interpreter has no + path by which to open it. Because these are program choices, a ``B`` entry carries no flags in the register string; ``F`` (pre-open a fixed interpreter) has no meaning for it. diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c index a3175521c8dd..9d7fc598d8c1 100644 --- a/fs/binfmt_misc.c +++ b/fs/binfmt_misc.c @@ -296,6 +296,7 @@ static int load_misc_binary(struct linux_binprm *bprm) const char *interpreter; struct file *interp_file; struct binfmt_misc *misc; + bool transparent = false; bool preserve_argv0; int retval; @@ -307,10 +308,6 @@ static int load_misc_binary(struct linux_binprm *bprm) if (!fmt) return -ENOEXEC; - /* Need to be able to load the file after exec */ - if (bprm->interp_flags & BINPRM_FLAGS_PATH_INACCESSIBLE) - return -ENOENT; - interpreter = entry_select_interpreter(fmt, bprm); if (IS_ERR(interpreter)) return PTR_ERR(interpreter); @@ -326,10 +323,13 @@ static int load_misc_binary(struct linux_binprm *bprm) /* Clear so it can't accumulate into a nested interpreter level. */ bprm->bpf_flags = 0; - preserve_argv0 = f & BPF_BINPRM_PRESERVE_ARGV0; + transparent = f & BPF_BINPRM_TRANSPARENT; + /* In transparent mode argv[0] is already the caller's. */ + preserve_argv0 = (f & BPF_BINPRM_PRESERVE_ARGV0) && !transparent; if (f & BPF_BINPRM_CREDENTIALS) bprm->execfd_creds = 1; - if (f & (BPF_BINPRM_CREDENTIALS | BPF_BINPRM_EXECFD)) + if (f & (BPF_BINPRM_CREDENTIALS | BPF_BINPRM_EXECFD | + BPF_BINPRM_TRANSPARENT)) bprm->have_execfd = 1; } else { preserve_argv0 = fmt->flags & MISC_FMT_PRESERVE_ARGV0; @@ -339,45 +339,63 @@ static int load_misc_binary(struct linux_binprm *bprm) bprm->have_execfd = 1; } - /* The entry's own choice - not one accumulated from an earlier level. */ - if (preserve_argv0) { - bprm->interp_flags |= BINPRM_FLAGS_PRESERVE_ARGV0; + if (transparent) { + /* + * Transparent execution: the interpreter takes the binary from + * AT_EXECFD, so leave the argument vector and bprm->interp as + * the caller set them. argv[0] and /proc/pid/cmdline then look + * like a direct execution of the binary, and a binary passed as + * an inaccessible O_CLOEXEC fd to execveat() can be run at all. + */ + + /* No argv is built, so drop any staged interpreter argument. */ + kfree(bprm->bpf_interp_arg); + bprm->bpf_interp_arg = NULL; } else { - retval = remove_arg_zero(bprm); - if (retval) - return retval; - } + /* The interpreter has to be able to load the binary by path. */ + if (bprm->interp_flags & BINPRM_FLAGS_PATH_INACCESSIBLE) + return -ENOENT; - /* make the binary the last argument to the interpreter */ - retval = copy_string_kernel(bprm->interp, bprm); - if (retval < 0) - return retval; - bprm->argc++; + /* The entry's own choice - not one accumulated from an earlier level. */ + if (preserve_argv0) { + bprm->interp_flags |= BINPRM_FLAGS_PRESERVE_ARGV0; + } else { + retval = remove_arg_zero(bprm); + if (retval) + return retval; + } - /* - * 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); + /* make the binary the last argument to the interpreter */ + retval = copy_string_kernel(bprm->interp, 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++; + /* + * A single optional argument to the interpreter, inserted + * between it and the binary like a #! interpreter line's. + */ + if (bprm->bpf_interp_arg) { + retval = copy_string_kernel(bprm->bpf_interp_arg, bprm); + if (retval < 0) + return retval; + bprm->argc++; + /* Consumed - don't leak it into a nested interpreter's argv. */ + kfree(bprm->bpf_interp_arg); + bprm->bpf_interp_arg = NULL; + } - /* Update interp in case binfmt_script needs it. */ - retval = bprm_change_interp(interpreter, bprm); - if (retval < 0) - return retval; + /* add the interp as argv[0] */ + retval = copy_string_kernel(interpreter, bprm); + if (retval < 0) + return retval; + bprm->argc++; + + /* Update interp in case binfmt_script needs it. */ + retval = bprm_change_interp(interpreter, bprm); + if (retval < 0) + return retval; + } if (fmt->flags & MISC_FMT_OPEN_FILE) { interp_file = file_clone_open(fmt->interp_file); diff --git a/fs/binfmt_misc_bpf.c b/fs/binfmt_misc_bpf.c index 00c787e8bdcc..fb5d0f465ccc 100644 --- a/fs/binfmt_misc_bpf.c +++ b/fs/binfmt_misc_bpf.c @@ -178,7 +178,9 @@ __bpf_kfunc int bpf_binprm_set_interp_arg(struct linux_binprm *bprm, * O flags: BPF_BINPRM_PRESERVE_ARGV0 keeps the caller's argv[0], * BPF_BINPRM_CREDENTIALS computes credentials from the binary, and * BPF_BINPRM_EXECFD hands the binary to the interpreter through AT_EXECFD. - * Calling it again replaces the flags, passing zero clears them again. + * BPF_BINPRM_TRANSPARENT additionally leaves the argument vector untouched, + * making the exec look like a direct execution of the binary. Calling it + * again replaces the flags, passing zero clears them again. * * Return: 0 on success, -EINVAL if @flags contains an unknown bit */ @@ -186,7 +188,7 @@ __bpf_kfunc int bpf_binprm_set_flags(struct linux_binprm *bprm, enum bpf_binprm_flags flags) { if (flags & ~(BPF_BINPRM_PRESERVE_ARGV0 | BPF_BINPRM_CREDENTIALS | - BPF_BINPRM_EXECFD)) + BPF_BINPRM_EXECFD | BPF_BINPRM_TRANSPARENT)) return -EINVAL; bprm->bpf_flags = flags; diff --git a/include/linux/binfmt_misc.h b/include/linux/binfmt_misc.h index d3112a00cc19..c43df749e021 100644 --- a/include/linux/binfmt_misc.h +++ b/include/linux/binfmt_misc.h @@ -16,6 +16,8 @@ struct user_namespace; * @BPF_BINPRM_CREDENTIALS: compute credentials from the binary; implies execfd * (like the 'C' flag) * @BPF_BINPRM_EXECFD: pass the binary via AT_EXECFD (like the 'O' flag) + * @BPF_BINPRM_TRANSPARENT: leave argv untouched, the interpreter takes the + * binary from AT_EXECFD; implies execfd * * Set from a load program with bpf_binprm_set_flags(). Unlike a static entry, * a bpf handler chooses these per exec rather than once at registration. @@ -24,6 +26,7 @@ enum bpf_binprm_flags { BPF_BINPRM_PRESERVE_ARGV0 = (1ULL << 0), BPF_BINPRM_CREDENTIALS = (1ULL << 1), BPF_BINPRM_EXECFD = (1ULL << 2), + BPF_BINPRM_TRANSPARENT = (1ULL << 3), }; /** -- 2.53.0