summaryrefslogtreecommitdiff
path: root/fs/exec.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/exec.c')
-rw-r--r--fs/exec.c86
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);