binfmt_misc currently supports an execution model where the registered interpreter becomes the executed program and the matched binary is handed to it as payload. The upcoming binfmt_misc loader mode inverts this. The matched binary remains the executed program and the registered interpreter is substituted into the role the binary's PT_INTERP would have played. Add the channel for that hand-over. bprm->loader carries an open_exec-style struct file reference from the binfmt_misc match to the binary format that consumes it. Unlike bprm->interpreter it does not request a restart of the format search. The stashing handler declines the exec with -ENOEXEC and the search continues to the real format in the same round. Establish the complete lifecycle up front so a stashed loader can neither leak nor be silently ignored. - Chain restart: if another format wins the round by staging bprm->interpreter (binfmt_script) the stashed loader belonged to the file being replaced. Drop it at the top of the swap block in exec_binprm(). - Unclaimed or error: free_bprm() releases a still-stashed loader next to the other bprm file references. - Silent non-substitution: a final format that reaches begin_new_exec() with a pending loader would run the binary while ignoring the override. Refuse with -ENOEXEC before the point of no return. Formats that do not know about the override (binfmt_flat, binfmt_elf_fdpic, out-of-tree) need no changes. Signed-off-by: Christian Brauner (Amutable) --- fs/exec.c | 9 +++++++++ include/linux/binfmts.h | 1 + 2 files changed, 10 insertions(+) diff --git a/fs/exec.c b/fs/exec.c index 45d416994682..797d9a0cf6c6 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1123,6 +1123,10 @@ int begin_new_exec(struct linux_binprm * bprm) struct task_struct *me = current; int retval; + /* A pending PT_INTERP substitution this format cannot consume. */ + if (bprm->loader) + return -ENOEXEC; + /* A declined execfd request has no executable for a later format. */ if (!bprm->executable) { bprm->have_execfd = 0; @@ -1435,6 +1439,8 @@ static void free_bprm(struct linux_binprm *bprm) if (bprm->old_mm) exec_mm_put_old(bprm->old_mm); do_close_execat(bprm->file); + /* An unconsumed PT_INTERP substitute from a binfmt_misc loader entry. */ + do_close_execat(bprm->loader); if (bprm->executable) { /* A transparent dispatch still holds the write denial. */ if (bprm->executable_denied) @@ -1757,6 +1763,9 @@ static int exec_binprm(struct linux_binprm *bprm) if (!bprm->interpreter) break; + /* A stashed PT_INTERP substitute belonged to the replaced file. */ + do_close_execat(no_free_ptr(bprm->loader)); + exec = bprm->file; bprm->file = bprm->interpreter; bprm->interpreter = NULL; diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h index ba5037b69866..aa6dac777173 100644 --- a/include/linux/binfmts.h +++ b/include/linux/binfmts.h @@ -64,6 +64,7 @@ struct linux_binprm { executable_denied:1; struct file *executable; /* Executable to pass to the interpreter */ struct file *interpreter; + struct file *loader; struct file *file; struct cred *cred; /* new credentials */ int unsafe; /* how unsafe this exec is (mask of LSM_UNSAFE_*) */ -- 2.53.0