]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
binfmt_misc: let bpf handlers pass an argument to the interpreter
authorChristian Brauner <brauner@kernel.org>
Tue, 14 Jul 2026 19:58:11 +0000 (21:58 +0200)
committerChristian Brauner <brauner@kernel.org>
Mon, 3 Aug 2026 08:08:42 +0000 (10:08 +0200)
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>
Documentation/admin-guide/binfmt-misc.rst
fs/binfmt_misc.c

index 113cc51e038c72705cbdb22a2d2224656b75ff3a..c2c18ca9ff8e5244d568426509694584f8964f26 100644 (file)
@@ -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.
index d5bb63b048ea860938f9eb4b760069f6fdcea8de..507f833a3179111d788f819f265742a4ecb00f96 100644 (file)
@@ -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)