mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 15:22:21 -04:00
binfmt_misc: let bpf handlers pass an argument to the interpreter
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. Link: https://patch.msgid.link/20260714-work-bpf-binfmt_misc-v2-6-57b7529c002c@kernel.org Reviewed-by: Farid Zakaria <farid.m.zakaria@gmail.com> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
@@ -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.
|
||||
|
||||
A handler is looked up only in the user namespace the struct_ops map was
|
||||
registered in. Handlers are not inherited, so an entry can only reference a
|
||||
handler registered in the same user namespace as its binfmt_misc instance.
|
||||
|
||||
@@ -259,23 +259,32 @@ static const char *entry_select_interpreter(const struct binfmt_misc_entry *e,
|
||||
if (!test_bit(MISC_FMT_BPF_BIT, &e->flags))
|
||||
return e->interpreter;
|
||||
|
||||
/* Drop any interpreter a previous chain level staged. */
|
||||
/* Drop any interpreter or flags a previous chain level staged. */
|
||||
kfree(bprm->bpf_interp);
|
||||
bprm->bpf_interp = NULL;
|
||||
bprm->bpf_flags = 0;
|
||||
|
||||
retval = e->bpf_ops->load(bprm);
|
||||
if (retval) {
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -313,12 +322,26 @@ static int load_misc_binary(struct linux_binprm *bprm)
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* 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)
|
||||
|
||||
Reference in New Issue
Block a user