Merge branch 'exec_rm_compat' of git://git.kernel.org/pub/scm/linux/kernel/git/oleg...
authorLinus Torvalds <torvalds@linux-foundation.org>
Mon, 23 May 2011 15:28:34 +0000 (08:28 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Mon, 23 May 2011 15:28:34 +0000 (08:28 -0700)
* 'exec_rm_compat' of git://git.kernel.org/pub/scm/linux/kernel/git/oleg/misc:
  exec: document acct_arg_size()
  exec: unify do_execve/compat_do_execve code
  exec: introduce struct user_arg_ptr
  exec: introduce get_user_arg_ptr() helper

fs/compat.c
fs/exec.c
include/linux/binfmts.h

index 72fe6cd..0ea0083 100644 (file)
@@ -1306,241 +1306,6 @@ compat_sys_openat(unsigned int dfd, const char __user *filename, int flags, int
        return do_sys_open(dfd, filename, flags, mode);
 }
 
-/*
- * compat_count() counts the number of arguments/envelopes. It is basically
- * a copy of count() from fs/exec.c, except that it works with 32 bit argv
- * and envp pointers.
- */
-static int compat_count(compat_uptr_t __user *argv, int max)
-{
-       int i = 0;
-
-       if (argv != NULL) {
-               for (;;) {
-                       compat_uptr_t p;
-
-                       if (get_user(p, argv))
-                               return -EFAULT;
-                       if (!p)
-                               break;
-                       argv++;
-                       if (i++ >= max)
-                               return -E2BIG;
-
-                       if (fatal_signal_pending(current))
-                               return -ERESTARTNOHAND;
-                       cond_resched();
-               }
-       }
-       return i;
-}
-
-/*
- * compat_copy_strings() is basically a copy of copy_strings() from fs/exec.c
- * except that it works with 32 bit argv and envp pointers.
- */
-static int compat_copy_strings(int argc, compat_uptr_t __user *argv,
-                               struct linux_binprm *bprm)
-{
-       struct page *kmapped_page = NULL;
-       char *kaddr = NULL;
-       unsigned long kpos = 0;
-       int ret;
-
-       while (argc-- > 0) {
-               compat_uptr_t str;
-               int len;
-               unsigned long pos;
-
-               if (get_user(str, argv+argc) ||
-                   !(len = strnlen_user(compat_ptr(str), MAX_ARG_STRLEN))) {
-                       ret = -EFAULT;
-                       goto out;
-               }
-
-               if (len > MAX_ARG_STRLEN) {
-                       ret = -E2BIG;
-                       goto out;
-               }
-
-               /* We're going to work our way backwords. */
-               pos = bprm->p;
-               str += len;
-               bprm->p -= len;
-
-               while (len > 0) {
-                       int offset, bytes_to_copy;
-
-                       if (fatal_signal_pending(current)) {
-                               ret = -ERESTARTNOHAND;
-                               goto out;
-                       }
-                       cond_resched();
-
-                       offset = pos % PAGE_SIZE;
-                       if (offset == 0)
-                               offset = PAGE_SIZE;
-
-                       bytes_to_copy = offset;
-                       if (bytes_to_copy > len)
-                               bytes_to_copy = len;
-
-                       offset -= bytes_to_copy;
-                       pos -= bytes_to_copy;
-                       str -= bytes_to_copy;
-                       len -= bytes_to_copy;
-
-                       if (!kmapped_page || kpos != (pos & PAGE_MASK)) {
-                               struct page *page;
-
-                               page = get_arg_page(bprm, pos, 1);
-                               if (!page) {
-                                       ret = -E2BIG;
-                                       goto out;
-                               }
-
-                               if (kmapped_page) {
-                                       flush_kernel_dcache_page(kmapped_page);
-                                       kunmap(kmapped_page);
-                                       put_page(kmapped_page);
-                               }
-                               kmapped_page = page;
-                               kaddr = kmap(kmapped_page);
-                               kpos = pos & PAGE_MASK;
-                               flush_cache_page(bprm->vma, kpos,
-                                                page_to_pfn(kmapped_page));
-                       }
-                       if (copy_from_user(kaddr+offset, compat_ptr(str),
-                                               bytes_to_copy)) {
-                               ret = -EFAULT;
-                               goto out;
-                       }
-               }
-       }
-       ret = 0;
-out:
-       if (kmapped_page) {
-               flush_kernel_dcache_page(kmapped_page);
-               kunmap(kmapped_page);
-               put_page(kmapped_page);
-       }
-       return ret;
-}
-
-/*
- * compat_do_execve() is mostly a copy of do_execve(), with the exception
- * that it processes 32 bit argv and envp pointers.
- */
-int compat_do_execve(char * filename,
-       compat_uptr_t __user *argv,
-       compat_uptr_t __user *envp,
-       struct pt_regs * regs)
-{
-       struct linux_binprm *bprm;
-       struct file *file;
-       struct files_struct *displaced;
-       bool clear_in_exec;
-       int retval;
-
-       retval = unshare_files(&displaced);
-       if (retval)
-               goto out_ret;
-
-       retval = -ENOMEM;
-       bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
-       if (!bprm)
-               goto out_files;
-
-       retval = prepare_bprm_creds(bprm);
-       if (retval)
-               goto out_free;
-
-       retval = check_unsafe_exec(bprm);
-       if (retval < 0)
-               goto out_free;
-       clear_in_exec = retval;
-       current->in_execve = 1;
-
-       file = open_exec(filename);
-       retval = PTR_ERR(file);
-       if (IS_ERR(file))
-               goto out_unmark;
-
-       sched_exec();
-
-       bprm->file = file;
-       bprm->filename = filename;
-       bprm->interp = filename;
-
-       retval = bprm_mm_init(bprm);
-       if (retval)
-               goto out_file;
-
-       bprm->argc = compat_count(argv, MAX_ARG_STRINGS);
-       if ((retval = bprm->argc) < 0)
-               goto out;
-
-       bprm->envc = compat_count(envp, MAX_ARG_STRINGS);
-       if ((retval = bprm->envc) < 0)
-               goto out;
-
-       retval = prepare_binprm(bprm);
-       if (retval < 0)
-               goto out;
-
-       retval = copy_strings_kernel(1, &bprm->filename, bprm);
-       if (retval < 0)
-               goto out;
-
-       bprm->exec = bprm->p;
-       retval = compat_copy_strings(bprm->envc, envp, bprm);
-       if (retval < 0)
-               goto out;
-
-       retval = compat_copy_strings(bprm->argc, argv, bprm);
-       if (retval < 0)
-               goto out;
-
-       retval = search_binary_handler(bprm, regs);
-       if (retval < 0)
-               goto out;
-
-       /* execve succeeded */
-       current->fs->in_exec = 0;
-       current->in_execve = 0;
-       acct_update_integrals(current);
-       free_bprm(bprm);
-       if (displaced)
-               put_files_struct(displaced);
-       return retval;
-
-out:
-       if (bprm->mm) {
-               acct_arg_size(bprm, 0);
-               mmput(bprm->mm);
-       }
-
-out_file:
-       if (bprm->file) {
-               allow_write_access(bprm->file);
-               fput(bprm->file);
-       }
-
-out_unmark:
-       if (clear_in_exec)
-               current->fs->in_exec = 0;
-       current->in_execve = 0;
-
-out_free:
-       free_bprm(bprm);
-
-out_files:
-       if (displaced)
-               reset_files_struct(displaced);
-out_ret:
-       return retval;
-}
-
 #define __COMPAT_NFDBITS       (8 * sizeof(compat_ulong_t))
 
 static int poll_select_copy_remaining(struct timespec *end_time, void __user *p,
index 8328beb..c016896 100644 (file)
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -55,6 +55,7 @@
 #include <linux/fs_struct.h>
 #include <linux/pipe_fs_i.h>
 #include <linux/oom.h>
+#include <linux/compat.h>
 
 #include <asm/uaccess.h>
 #include <asm/mmu_context.h>
@@ -166,8 +167,13 @@ out:
 }
 
 #ifdef CONFIG_MMU
-
-void acct_arg_size(struct linux_binprm *bprm, unsigned long pages)
+/*
+ * The nascent bprm->mm is not visible until exec_mmap() but it can
+ * use a lot of memory, account these pages in current->mm temporary
+ * for oom_badness()->get_mm_rss(). Once exec succeeds or fails, we
+ * change the counter back via acct_arg_size(0).
+ */
+static void acct_arg_size(struct linux_binprm *bprm, unsigned long pages)
 {
        struct mm_struct *mm = current->mm;
        long diff = (long)(pages - bprm->vma_pages);
@@ -186,7 +192,7 @@ void acct_arg_size(struct linux_binprm *bprm, unsigned long pages)
 #endif
 }
 
-struct page *get_arg_page(struct linux_binprm *bprm, unsigned long pos,
+static struct page *get_arg_page(struct linux_binprm *bprm, unsigned long pos,
                int write)
 {
        struct page *page;
@@ -305,11 +311,11 @@ static bool valid_arg_len(struct linux_binprm *bprm, long len)
 
 #else
 
-void acct_arg_size(struct linux_binprm *bprm, unsigned long pages)
+static inline void acct_arg_size(struct linux_binprm *bprm, unsigned long pages)
 {
 }
 
-struct page *get_arg_page(struct linux_binprm *bprm, unsigned long pos,
+static struct page *get_arg_page(struct linux_binprm *bprm, unsigned long pos,
                int write)
 {
        struct page *page;
@@ -398,22 +404,56 @@ err:
        return err;
 }
 
+struct user_arg_ptr {
+#ifdef CONFIG_COMPAT
+       bool is_compat;
+#endif
+       union {
+               const char __user *const __user *native;
+#ifdef CONFIG_COMPAT
+               compat_uptr_t __user *compat;
+#endif
+       } ptr;
+};
+
+static const char __user *get_user_arg_ptr(struct user_arg_ptr argv, int nr)
+{
+       const char __user *native;
+
+#ifdef CONFIG_COMPAT
+       if (unlikely(argv.is_compat)) {
+               compat_uptr_t compat;
+
+               if (get_user(compat, argv.ptr.compat + nr))
+                       return ERR_PTR(-EFAULT);
+
+               return compat_ptr(compat);
+       }
+#endif
+
+       if (get_user(native, argv.ptr.native + nr))
+               return ERR_PTR(-EFAULT);
+
+       return native;
+}
+
 /*
  * count() counts the number of strings in array ARGV.
  */
-static int count(const char __user * const __user * argv, int max)
+static int count(struct user_arg_ptr argv, int max)
 {
        int i = 0;
 
-       if (argv != NULL) {
+       if (argv.ptr.native != NULL) {
                for (;;) {
-                       const char __user * p;
+                       const char __user *p = get_user_arg_ptr(argv, i);
 
-                       if (get_user(p, argv))
-                               return -EFAULT;
                        if (!p)
                                break;
-                       argv++;
+
+                       if (IS_ERR(p))
+                               return -EFAULT;
+
                        if (i++ >= max)
                                return -E2BIG;
 
@@ -430,7 +470,7 @@ static int count(const char __user * const __user * argv, int max)
  * processes's memory to the new process's stack.  The call to get_user_pages()
  * ensures the destination page is created and not swapped out.
  */
-static int copy_strings(int argc, const char __user *const __user *argv,
+static int copy_strings(int argc, struct user_arg_ptr argv,
                        struct linux_binprm *bprm)
 {
        struct page *kmapped_page = NULL;
@@ -443,16 +483,18 @@ static int copy_strings(int argc, const char __user *const __user *argv,
                int len;
                unsigned long pos;
 
-               if (get_user(str, argv+argc) ||
-                               !(len = strnlen_user(str, MAX_ARG_STRLEN))) {
-                       ret = -EFAULT;
+               ret = -EFAULT;
+               str = get_user_arg_ptr(argv, argc);
+               if (IS_ERR(str))
                        goto out;
-               }
 
-               if (!valid_arg_len(bprm, len)) {
-                       ret = -E2BIG;
+               len = strnlen_user(str, MAX_ARG_STRLEN);
+               if (!len)
+                       goto out;
+
+               ret = -E2BIG;
+               if (!valid_arg_len(bprm, len))
                        goto out;
-               }
 
                /* We're going to work our way backwords. */
                pos = bprm->p;
@@ -519,14 +561,19 @@ out:
 /*
  * Like copy_strings, but get argv and its values from kernel memory.
  */
-int copy_strings_kernel(int argc, const char *const *argv,
+int copy_strings_kernel(int argc, const char *const *__argv,
                        struct linux_binprm *bprm)
 {
        int r;
        mm_segment_t oldfs = get_fs();
+       struct user_arg_ptr argv = {
+               .ptr.native = (const char __user *const  __user *)__argv,
+       };
+
        set_fs(KERNEL_DS);
-       r = copy_strings(argc, (const char __user *const  __user *)argv, bprm);
+       r = copy_strings(argc, argv, bprm);
        set_fs(oldfs);
+
        return r;
 }
 EXPORT_SYMBOL(copy_strings_kernel);
@@ -1379,10 +1426,10 @@ EXPORT_SYMBOL(search_binary_handler);
 /*
  * sys_execve() executes a new program.
  */
-int do_execve(const char * filename,
-       const char __user *const __user *argv,
-       const char __user *const __user *envp,
-       struct pt_regs * regs)
+static int do_execve_common(const char *filename,
+                               struct user_arg_ptr argv,
+                               struct user_arg_ptr envp,
+                               struct pt_regs *regs)
 {
        struct linux_binprm *bprm;
        struct file *file;
@@ -1489,6 +1536,34 @@ out_ret:
        return retval;
 }
 
+int do_execve(const char *filename,
+       const char __user *const __user *__argv,
+       const char __user *const __user *__envp,
+       struct pt_regs *regs)
+{
+       struct user_arg_ptr argv = { .ptr.native = __argv };
+       struct user_arg_ptr envp = { .ptr.native = __envp };
+       return do_execve_common(filename, argv, envp, regs);
+}
+
+#ifdef CONFIG_COMPAT
+int compat_do_execve(char *filename,
+       compat_uptr_t __user *__argv,
+       compat_uptr_t __user *__envp,
+       struct pt_regs *regs)
+{
+       struct user_arg_ptr argv = {
+               .is_compat = true,
+               .ptr.compat = __argv,
+       };
+       struct user_arg_ptr envp = {
+               .is_compat = true,
+               .ptr.compat = __envp,
+       };
+       return do_execve_common(filename, argv, envp, regs);
+}
+#endif
+
 void set_binfmt(struct linux_binfmt *new)
 {
        struct mm_struct *mm = current->mm;
index c3d6512..8845613 100644 (file)
@@ -60,10 +60,6 @@ struct linux_binprm {
        unsigned long loader, exec;
 };
 
-extern void acct_arg_size(struct linux_binprm *bprm, unsigned long pages);
-extern struct page *get_arg_page(struct linux_binprm *bprm, unsigned long pos,
-                                       int write);
-
 #define BINPRM_FLAGS_ENFORCE_NONDUMP_BIT 0
 #define BINPRM_FLAGS_ENFORCE_NONDUMP (1 << BINPRM_FLAGS_ENFORCE_NONDUMP_BIT)