exec: Implement kernel_execve
authorEric W. Biederman <ebiederm@xmission.com>
Mon, 13 Jul 2020 17:06:48 +0000 (12:06 -0500)
committerEric W. Biederman <ebiederm@xmission.com>
Tue, 21 Jul 2020 13:24:52 +0000 (08:24 -0500)
To allow the kernel not to play games with set_fs to call exec
implement kernel_execve.  The function kernel_execve takes pointers
into kernel memory and copies the values pointed to onto the new
userspace stack.

The calls with arguments from kernel space of do_execve are replaced
with calls to kernel_execve.

The calls do_execve and do_execveat are made static as there are now
no callers outside of exec.

The comments that mention do_execve are updated to refer to
kernel_execve or execve depending on the circumstances.  In addition
to correcting the comments, this makes it easy to grep for do_execve
and verify it is not used.

Inspired-by: https://lkml.kernel.org/r/20200627072704.2447163-1-hch@lst.de
Reviewed-by: Kees Cook <keescook@chromium.org>
Link: https://lkml.kernel.org/r/87wo365ikj.fsf@x220.int.ebiederm.org
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
arch/x86/entry/entry_32.S
arch/x86/entry/entry_64.S
arch/x86/kernel/unwind_frame.c
fs/exec.c
include/linux/binfmts.h
init/main.c
kernel/umh.c
security/tomoyo/common.h
security/tomoyo/domain.c
security/tomoyo/tomoyo.c

index 024d7d276cd40bac51d405357209c9dd4634f0f1..8f4e085ee06d6ba1129d9ecd7c5d914b536c26e4 100644 (file)
@@ -854,7 +854,7 @@ SYM_CODE_START(ret_from_fork)
        CALL_NOSPEC ebx
        /*
         * A kernel thread is allowed to return here after successfully
-        * calling do_execve().  Exit to userspace to complete the execve()
+        * calling kernel_execve().  Exit to userspace to complete the execve()
         * syscall.
         */
        movl    $0, PT_EAX(%esp)
index d2a00c97e53f608ad67a4e24db1f5c64f1b9e880..73c7e255256bf296390ee7543f0e8b3e3ae0c078 100644 (file)
@@ -293,7 +293,7 @@ SYM_CODE_START(ret_from_fork)
        CALL_NOSPEC rbx
        /*
         * A kernel thread is allowed to return here after successfully
-        * calling do_execve().  Exit to userspace to complete the execve()
+        * calling kernel_execve().  Exit to userspace to complete the execve()
         * syscall.
         */
        movq    $0, RAX(%rsp)
index 722a85f3b2dd09de180bb9f8d3340469f4165b84..e40b4942157fb8e7aa8c93896a66244784c52eb8 100644 (file)
@@ -275,7 +275,7 @@ bool unwind_next_frame(struct unwind_state *state)
                 * This user_mode() check is slightly broader than a PF_KTHREAD
                 * check because it also catches the awkward situation where a
                 * newly forked kthread transitions into a user task by calling
-                * do_execve(), which eventually clears PF_KTHREAD.
+                * kernel_execve(), which eventually clears PF_KTHREAD.
                 */
                if (!user_mode(regs))
                        goto the_end;
index f8135dc149b36b55cd0d1660202c81bcbf2ed4b9..3698252719a33d21049d0f4f58b0ca898bc4d29d 100644 (file)
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -448,6 +448,23 @@ static int count(struct user_arg_ptr argv, int max)
        return i;
 }
 
+static int count_strings_kernel(const char *const *argv)
+{
+       int i;
+
+       if (!argv)
+               return 0;
+
+       for (i = 0; argv[i]; ++i) {
+               if (i >= MAX_ARG_STRINGS)
+                       return -E2BIG;
+               if (fatal_signal_pending(current))
+                       return -ERESTARTNOHAND;
+               cond_resched();
+       }
+       return i;
+}
+
 static int bprm_stack_limits(struct linux_binprm *bprm)
 {
        unsigned long limit, ptr_size;
@@ -624,6 +641,20 @@ int copy_string_kernel(const char *arg, struct linux_binprm *bprm)
 }
 EXPORT_SYMBOL(copy_string_kernel);
 
+static int copy_strings_kernel(int argc, const char *const *argv,
+                              struct linux_binprm *bprm)
+{
+       while (argc-- > 0) {
+               int ret = copy_string_kernel(argv[argc], bprm);
+               if (ret < 0)
+                       return ret;
+               if (fatal_signal_pending(current))
+                       return -ERESTARTNOHAND;
+               cond_resched();
+       }
+       return 0;
+}
+
 #ifdef CONFIG_MMU
 
 /*
@@ -1991,7 +2022,60 @@ out_ret:
        return retval;
 }
 
-int do_execve(struct filename *filename,
+int kernel_execve(const char *kernel_filename,
+                 const char *const *argv, const char *const *envp)
+{
+       struct filename *filename;
+       struct linux_binprm *bprm;
+       int fd = AT_FDCWD;
+       int retval;
+
+       filename = getname_kernel(kernel_filename);
+       if (IS_ERR(filename))
+               return PTR_ERR(filename);
+
+       bprm = alloc_bprm(fd, filename);
+       if (IS_ERR(bprm)) {
+               retval = PTR_ERR(bprm);
+               goto out_ret;
+       }
+
+       retval = count_strings_kernel(argv);
+       if (retval < 0)
+               goto out_free;
+       bprm->argc = retval;
+
+       retval = count_strings_kernel(envp);
+       if (retval < 0)
+               goto out_free;
+       bprm->envc = retval;
+
+       retval = bprm_stack_limits(bprm);
+       if (retval < 0)
+               goto out_free;
+
+       retval = copy_string_kernel(bprm->filename, bprm);
+       if (retval < 0)
+               goto out_free;
+       bprm->exec = bprm->p;
+
+       retval = copy_strings_kernel(bprm->envc, envp, bprm);
+       if (retval < 0)
+               goto out_free;
+
+       retval = copy_strings_kernel(bprm->argc, argv, bprm);
+       if (retval < 0)
+               goto out_free;
+
+       retval = bprm_execve(bprm, fd, filename, 0);
+out_free:
+       free_bprm(bprm);
+out_ret:
+       putname(filename);
+       return retval;
+}
+
+static int do_execve(struct filename *filename,
        const char __user *const __user *__argv,
        const char __user *const __user *__envp)
 {
@@ -2000,7 +2084,7 @@ int do_execve(struct filename *filename,
        return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
 }
 
-int do_execveat(int fd, struct filename *filename,
+static int do_execveat(int fd, struct filename *filename,
                const char __user *const __user *__argv,
                const char __user *const __user *__envp,
                int flags)
index 8e9e1b0c8eb83a029b90d7d75397ddd22fd35d3c..0571701ab1c57cc0d3dcddb119ea0c503e1ba641 100644 (file)
@@ -135,12 +135,7 @@ int copy_string_kernel(const char *arg, struct linux_binprm *bprm);
 extern void set_binfmt(struct linux_binfmt *new);
 extern ssize_t read_code(struct file *, unsigned long, loff_t, size_t);
 
-extern int do_execve(struct filename *,
-                    const char __user * const __user *,
-                    const char __user * const __user *);
-extern int do_execveat(int, struct filename *,
-                      const char __user * const __user *,
-                      const char __user * const __user *,
-                      int);
+int kernel_execve(const char *filename,
+                 const char *const *argv, const char *const *envp);
 
 #endif /* _LINUX_BINFMTS_H */
index 0ead83e86b5aa298e769b51be5de2f6a94a7c44b..78ccec5c28f32d03ed648ed2557babdd835296d9 100644 (file)
@@ -1329,9 +1329,7 @@ static int run_init_process(const char *init_filename)
        pr_debug("  with environment:\n");
        for (p = envp_init; *p; p++)
                pr_debug("    %s\n", *p);
-       return do_execve(getname_kernel(init_filename),
-               (const char __user *const __user *)argv_init,
-               (const char __user *const __user *)envp_init);
+       return kernel_execve(init_filename, argv_init, envp_init);
 }
 
 static int try_to_run_init_process(const char *init_filename)
index 6ca2096298b97419bdb4fd5209d6452dcfd1c141..a25433f9cd9ad12aa383638efb86d93d5438aa14 100644 (file)
@@ -98,9 +98,9 @@ static int call_usermodehelper_exec_async(void *data)
 
        commit_creds(new);
 
-       retval = do_execve(getname_kernel(sub_info->path),
-                          (const char __user *const __user *)sub_info->argv,
-                          (const char __user *const __user *)sub_info->envp);
+       retval = kernel_execve(sub_info->path,
+                              (const char *const *)sub_info->argv,
+                              (const char *const *)sub_info->envp);
 out:
        sub_info->retval = retval;
        /*
index 050473df5809f13f467c4cdebffd981a773948a9..85246b9df7ca9711708ff63ddd515298d656cee3 100644 (file)
@@ -425,7 +425,7 @@ struct tomoyo_request_info {
        struct tomoyo_obj_info *obj;
        /*
         * For holding parameters specific to execve() request.
-        * NULL if not dealing do_execve().
+        * NULL if not dealing execve().
         */
        struct tomoyo_execve *ee;
        struct tomoyo_domain_info *domain;
index 7869d6a9980bfc4e7c0cf70a826d7b5661aaef67..53b3e1f5f22704681b84459a5622e84a400f17b6 100644 (file)
@@ -767,7 +767,7 @@ retry:
 
        /*
         * Check for domain transition preference if "file execute" matched.
-        * If preference is given, make do_execve() fail if domain transition
+        * If preference is given, make execve() fail if domain transition
         * has failed, for domain transition preference should be used with
         * destination domain defined.
         */
@@ -810,7 +810,7 @@ force_reset_domain:
                snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "<%s>",
                         candidate->name);
                /*
-                * Make do_execve() fail if domain transition across namespaces
+                * Make execve() fail if domain transition across namespaces
                 * has failed.
                 */
                reject_on_transition_failure = true;
index f9adddc42ac8de9dcbcb0c917b906d5a8658ef48..1f3cd432d830875d47e30d83d96ba4f2cd07b3af 100644 (file)
@@ -93,7 +93,7 @@ static int tomoyo_bprm_check_security(struct linux_binprm *bprm)
        struct tomoyo_task *s = tomoyo_task(current);
 
        /*
-        * Execute permission is checked against pathname passed to do_execve()
+        * Execute permission is checked against pathname passed to execve()
         * using current domain.
         */
        if (!s->old_domain_info) {
@@ -307,7 +307,7 @@ static int tomoyo_file_fcntl(struct file *file, unsigned int cmd,
  */
 static int tomoyo_file_open(struct file *f)
 {
-       /* Don't check read permission here if called from do_execve(). */
+       /* Don't check read permission here if called from execve(). */
        if (current->in_execve)
                return 0;
        return tomoyo_check_open_permission(tomoyo_domain(), &f->f_path,