Merge branch 'proc-cmdline'
authorLinus Torvalds <torvalds@linux-foundation.org>
Sat, 9 Jun 2018 22:31:35 +0000 (15:31 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sat, 9 Jun 2018 22:31:35 +0000 (15:31 -0700)
Merge proc_cmdline simplifications.

This re-writes the get_mm_cmdline() logic to be rather simpler than it
used to be, and makes the semantics for "cmdline goes past the end of
the original area" more natural.

You _can_ use prctl(PR_SET_MM) to just point your command line somewhere
else entirely, but the traditional model is to just edit things in place
and that still needs to continue to work.  At least this way the code
makes some sense.

* proc-cmdline:
  fs/proc: simplify and clarify get_mm_cmdline() function
  fs/proc: re-factor proc_pid_cmdline_read() a bit

1  2 
fs/proc/base.c

diff --cc fs/proc/base.c
index 8358c5330c0b1abd8530c583b75af33b47a74889,2665bbbb4cca82c2c0f1e56e5b0d6a59d3cf67f3..4aa9ce5df02ffc9b6daf9d1d47157f5aee660799
@@@ -205,142 -205,129 +205,129 @@@ static int proc_root_link(struct dentr
        return result;
  }
  
- static ssize_t proc_pid_cmdline_read(struct file *file, char __user *buf,
-                                    size_t _count, loff_t *pos)
+ static ssize_t get_mm_cmdline(struct mm_struct *mm, char __user *buf,
+                             size_t count, loff_t *ppos)
  {
-       struct task_struct *tsk;
-       struct mm_struct *mm;
-       char *page;
-       unsigned long count = _count;
        unsigned long arg_start, arg_end, env_start, env_end;
-       unsigned long len1, len2;
-       char __user *buf0 = buf;
-       struct {
-               unsigned long p;
-               unsigned long len;
-       } cmdline[2];
-       char c;
-       int rv;
-       BUG_ON(*pos < 0);
+       unsigned long pos, len;
+       char *page;
  
-       tsk = get_proc_task(file_inode(file));
-       if (!tsk)
-               return -ESRCH;
-       mm = get_task_mm(tsk);
-       put_task_struct(tsk);
-       if (!mm)
-               return 0;
        /* Check if process spawned far enough to have cmdline. */
-       if (!mm->env_end) {
-               rv = 0;
-               goto out_mmput;
-       }
-       page = (char *)__get_free_page(GFP_KERNEL);
-       if (!page) {
-               rv = -ENOMEM;
-               goto out_mmput;
-       }
+       if (!mm->env_end)
+               return 0;
  
 -      down_read(&mm->mmap_sem);
 +      spin_lock(&mm->arg_lock);
        arg_start = mm->arg_start;
        arg_end = mm->arg_end;
        env_start = mm->env_start;
        env_end = mm->env_end;
 -      up_read(&mm->mmap_sem);
 +      spin_unlock(&mm->arg_lock);
  
-       BUG_ON(arg_start > arg_end);
-       BUG_ON(env_start > env_end);
-       len1 = arg_end - arg_start;
-       len2 = env_end - env_start;
-       /* Empty ARGV. */
-       if (len1 == 0)
-               goto end;
+       if (arg_start >= arg_end)
+               return 0;
  
        /*
-        * Inherently racy -- command line shares address space
-        * with code and data.
+        * We have traditionally allowed the user to re-write
+        * the argument strings and overflow the end result
+        * into the environment section. But only do that if
+        * the environment area is contiguous to the arguments.
         */
-       if (access_remote_vm(mm, arg_end - 1, &c, 1, FOLL_ANON) != 1)
-               goto end;
-       cmdline[0].p = arg_start;
-       cmdline[0].len = len1;
-       if (c == '\0') {
-               /* Command line (set of strings) occupies whole ARGV. */
-               cmdline[1].len = 0;
-       } else {
-               /*
-                * Command line (1 string) occupies ARGV and
-                * extends into ENVP.
-                */
-               cmdline[1].p = env_start;
-               cmdline[1].len = len2;
-       }
+       if (env_start != arg_end || env_start >= env_end)
+               env_start = env_end = arg_end;
+       /* We're not going to care if "*ppos" has high bits set */
+       pos = arg_start + *ppos;
  
-       {
-               loff_t pos1 = *pos;
-               unsigned int i;
+       /* .. but we do check the result is in the proper range */
+       if (pos < arg_start || pos >= env_end)
+               return 0;
  
-               i = 0;
-               while (i < 2 && pos1 >= cmdline[i].len) {
-                       pos1 -= cmdline[i].len;
-                       i++;
+       /* .. and we never go past env_end */
+       if (env_end - pos < count)
+               count = env_end - pos;
+       page = (char *)__get_free_page(GFP_KERNEL);
+       if (!page)
+               return -ENOMEM;
+       len = 0;
+       while (count) {
+               int got;
+               size_t size = min_t(size_t, PAGE_SIZE, count);
+               got = access_remote_vm(mm, pos, page, size, FOLL_ANON);
+               if (got <= 0)
+                       break;
+               /* Don't walk past a NUL character once you hit arg_end */
+               if (pos + got >= arg_end) {
+                       int n = 0;
+                       /*
+                        * If we started before 'arg_end' but ended up
+                        * at or after it, we start the NUL character
+                        * check at arg_end-1 (where we expect the normal
+                        * EOF to be).
+                        *
+                        * NOTE! This is smaller than 'got', because
+                        * pos + got >= arg_end
+                        */
+                       if (pos < arg_end)
+                               n = arg_end - pos - 1;
+                       /* Cut off at first NUL after 'n' */
+                       got = n + strnlen(page+n, got-n);
+                       if (!got)
+                               break;
                }
-               while (i < 2) {
-                       unsigned long p;
-                       unsigned long len;
-                       p = cmdline[i].p + pos1;
-                       len = cmdline[i].len - pos1;
-                       while (count > 0 && len > 0) {
-                               unsigned int nr_read, nr_write;
-                               nr_read = min3(count, len, PAGE_SIZE);
-                               nr_read = access_remote_vm(mm, p, page, nr_read, FOLL_ANON);
-                               if (nr_read == 0)
-                                       goto end;
-                               /*
-                                * Command line can be shorter than whole ARGV
-                                * even if last "marker" byte says it is not.
-                                */
-                               if (c == '\0')
-                                       nr_write = nr_read;
-                               else
-                                       nr_write = strnlen(page, nr_read);
-                               if (copy_to_user(buf, page, nr_write)) {
-                                       rv = -EFAULT;
-                                       goto out_free_page;
-                               }
-                               p       += nr_write;
-                               len     -= nr_write;
-                               buf     += nr_write;
-                               count   -= nr_write;
-                               if (nr_write < nr_read)
-                                       goto end;
-                       }
-                       /* Only first chunk can be read partially. */
-                       pos1 = 0;
-                       i++;
+               got -= copy_to_user(buf, page, got);
+               if (unlikely(!got)) {
+                       if (!len)
+                               len = -EFAULT;
+                       break;
                }
+               pos += got;
+               buf += got;
+               len += got;
+               count -= got;
        }
  
- end:
-       *pos += buf - buf0;
-       rv = buf - buf0;
- out_free_page:
        free_page((unsigned long)page);
- out_mmput:
+       return len;
+ }
+ static ssize_t get_task_cmdline(struct task_struct *tsk, char __user *buf,
+                               size_t count, loff_t *pos)
+ {
+       struct mm_struct *mm;
+       ssize_t ret;
+       mm = get_task_mm(tsk);
+       if (!mm)
+               return 0;
+       ret = get_mm_cmdline(mm, buf, count, pos);
        mmput(mm);
-       return rv;
+       return ret;
+ }
+ static ssize_t proc_pid_cmdline_read(struct file *file, char __user *buf,
+                                    size_t count, loff_t *pos)
+ {
+       struct task_struct *tsk;
+       ssize_t ret;
+       BUG_ON(*pos < 0);
+       tsk = get_proc_task(file_inode(file));
+       if (!tsk)
+               return -ESRCH;
+       ret = get_task_cmdline(tsk, buf, count, pos);
+       put_task_struct(tsk);
+       if (ret > 0)
+               *pos += ret;
+       return ret;
  }
  
  static const struct file_operations proc_pid_cmdline_ops = {