A bpf binfmt_misc handler selects an interpreter but, unlike binfmt_script, load_misc_binary() builds the argument vector as just [interpreter, binary, ...] with no slot for an argument to the interpreter. A handler that wants to reproduce a #! line therefore cannot express its single optional argument, e.g. a handler that resolves $ORIGIN in a script's #! path loses the argument that followed the interpreter. Have load_misc_binary() consume the argument staged through the bpf_binprm_set_interp_arg() kfunc and insert it between the interpreter and the binary - the same position and single-argument semantics binfmt_script gives the argument of a #! line. The argument is cleared once spliced into the argument vector, and a load program that fails after staging one has it dropped on the way out: whether the exec fails or -ENOEXEC hands the binary back to the remaining formats, a stale argument cannot leak into a nested interpreter's argv. This also lets static-style handlers pass a fixed interpreter argument, which plain binfmt_misc has never been able to express. Signed-off-by: Christian Brauner (Amutable) --- Documentation/admin-guide/binfmt-misc.rst | 6 ++++++ fs/binfmt_misc.c | 30 ++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Documentation/admin-guide/binfmt-misc.rst b/Documentation/admin-guide/binfmt-misc.rst index 45541604d528..6128057160e3 100644 --- a/Documentation/admin-guide/binfmt-misc.rst +++ b/Documentation/admin-guide/binfmt-misc.rst @@ -129,6 +129,12 @@ entries; ``-ENOEXEC`` lets the remaining binary formats have a go. The interpreter is opened with the credentials of the task doing the exec, exactly as a statically registered interpreter would be. +The ``load`` program can also pass a single argument to the interpreter with +the ``bpf_binprm_set_interp_arg()`` kfunc. It is inserted between the +interpreter and the binary, exactly like the optional argument of a ``#!`` +interpreter line, e.g. for a handler that resolves ``$ORIGIN`` in a script's +``#!`` path and needs to preserve the argument that followed it. + Handlers are looked up in the user namespace the struct_ops map was registered in, falling back to ancestor namespaces, mirroring how binfmt_misc instances themselves are looked up. The entry keeps the handler diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c index a8f641c4ec2b..3fbc51197c76 100644 --- a/fs/binfmt_misc.c +++ b/fs/binfmt_misc.c @@ -268,14 +268,22 @@ static const char *entry_select_interpreter(const struct binfmt_misc_entry *e, /* Keep a program-supplied error within errno range. */ if (retval > 0 || retval < -MAX_ERRNO) retval = -ENOEXEC; - return ERR_PTR(retval); + goto drop_staged; } /* Selecting an interpreter is part of the contract. */ - if (!bprm->bpf_interp) - return ERR_PTR(-ENOEXEC); + if (!bprm->bpf_interp) { + retval = -ENOEXEC; + goto drop_staged; + } return bprm->bpf_interp; + +drop_staged: + /* A failing load leaves nothing behind for later entries. */ + kfree(bprm->bpf_interp_arg); + bprm->bpf_interp_arg = NULL; + return ERR_PTR(retval); } /* @@ -316,12 +324,26 @@ static int load_misc_binary(struct linux_binprm *bprm) if (fmt->flags & MISC_FMT_OPEN_BINARY) bprm->have_execfd = 1; - /* make argv[1] be the path to the binary */ + /* 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) -- 2.53.0