diff options
Diffstat (limited to 'fs/exec.c')
| -rw-r--r-- | fs/exec.c | 86 |
1 files changed, 75 insertions, 11 deletions
diff --git a/fs/exec.c b/fs/exec.c index 41e1684d999c..a14f28b15607 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1102,6 +1102,17 @@ void __set_task_comm(struct task_struct *tsk, const char *buf, bool exec) } /* + * The file the process presents as: its exe link and comm. A transparent + * dispatch presents as the binary, which is bprm->executable. + */ +static struct file *bprm_identity_file(const struct linux_binprm *bprm) +{ + if (bprm->interp_flags & BINPRM_FLAGS_TRANSPARENT_INTERP) + return bprm->executable; + return bprm->file; +} + +/* * Calling this is the point of no return. None of the failures will be * seen by userspace since either the process is already taking a fatal * signal (via de_thread() or coredump), or will have SEGV raised @@ -1112,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; + /* Once we are committed compute the creds */ retval = bprm_creds_from_file(bprm); if (retval) @@ -1151,7 +1166,7 @@ int begin_new_exec(struct linux_binprm * bprm) * not visible until then. Doing it here also ensures * we don't race against replace_mm_exe_file(). */ - retval = set_mm_exe_file(bprm->mm, bprm->file); + retval = set_mm_exe_file(bprm->mm, bprm_identity_file(bprm)); if (retval) goto out; @@ -1241,6 +1256,8 @@ int begin_new_exec(struct linux_binprm * bprm) * Let's fix it up to be something reasonable. */ if (bprm->comm_from_dentry) { + struct file *comm_file = bprm_identity_file(bprm); + /* * Hold RCU lock to keep the name from being freed behind our back. * Use acquire semantics to make sure the terminating NUL from @@ -1250,7 +1267,7 @@ int begin_new_exec(struct linux_binprm * bprm) * detecting a concurrent rename and just want a terminated name. */ rcu_read_lock(); - __set_task_comm(me, smp_load_acquire(&bprm->file->f_path.dentry->d_name.name), + __set_task_comm(me, smp_load_acquire(&comm_file->f_path.dentry->d_name.name), true); rcu_read_unlock(); } else { @@ -1291,10 +1308,17 @@ int begin_new_exec(struct linux_binprm * bprm) /* Pass the opened binary to the interpreter. */ if (bprm->have_execfd) { - retval = FD_ADD(0, bprm->executable); - if (retval < 0) - goto out_unlock; + struct file *executable = bprm->executable; + + /* mm->exe_file carries its own write denial now so drop it. */ + exe_file_allow_write_access(executable); bprm->executable = NULL; + retval = FD_ADD(0, executable); + if (retval < 0) { + /* The reference was not consumed. */ + fput(executable); + goto out_unlock; + } bprm->execfd = retval; } return 0; @@ -1394,6 +1418,39 @@ static void do_close_execat(struct file *file) fput(file); } +/** + * bprm_open_interpreter - open the interpreter the binary asks for + * @bprm: binary that is being executed + * @path: the interpreter path named in the binary's PT_INTERP + * + * A binfmt_misc loader entry substitutes for the interpreter the binary + * names. Hand out the stashed substitute if there is one and open @path + * if there is not. The caller owns the reference either way and releases + * it like any other open_exec() one. + * + * Return: the interpreter on success, an ERR_PTR on failure + */ +struct file *bprm_open_interpreter(struct linux_binprm *bprm, const char *path) +{ + if (bprm->loader) + return no_free_ptr(bprm->loader); + return open_exec(path); +} + +/** + * bprm_drop_loader - discard a PT_INTERP substitute that does not apply + * @bprm: binary that is being executed + * + * A binary without PT_INTERP has nothing to substitute for, so drop the + * override and let the binary load natively rather than have + * begin_new_exec() refuse it. A no-op once bprm_open_interpreter() took + * the substitute. + */ +void bprm_drop_loader(struct linux_binprm *bprm) +{ + do_close_execat(no_free_ptr(bprm->loader)); +} + static void free_bprm(struct linux_binprm *bprm) { if (bprm->mm) { @@ -1413,12 +1470,15 @@ static void free_bprm(struct linux_binprm *bprm) if (bprm->old_mm) exec_mm_put_old(bprm->old_mm); do_close_execat(bprm->file); - if (bprm->executable) - fput(bprm->executable); + /* An unconsumed PT_INTERP substitute from a binfmt_misc loader entry. */ + bprm_drop_loader(bprm); + do_close_execat(bprm->executable); /* If a binfmt changed the interp, free it. */ if (bprm->interp != bprm->filename) kfree(bprm->interp); kfree(bprm->bpf_interp); + if (bprm->bpf_interp_file) + fput(bprm->bpf_interp_file); kfree(bprm->bpf_interp_arg); kfree(bprm->fdpath); kfree(bprm); @@ -1731,19 +1791,23 @@ static int exec_binprm(struct linux_binprm *bprm) if (!bprm->interpreter) break; + /* A stashed PT_INTERP substitute belonged to the replaced file. */ + bprm_drop_loader(bprm); + exec = bprm->file; bprm->file = bprm->interpreter; bprm->interpreter = NULL; - exe_file_allow_write_access(exec); if (unlikely(bprm->have_execfd)) { if (bprm->executable) { - fput(exec); + do_close_execat(exec); return -ENOEXEC; } + /* Kept for AT_EXECFD; the write denial rides along until hand-over. */ bprm->executable = exec; - } else - fput(exec); + } else { + do_close_execat(exec); + } } audit_bprm(bprm); |
