/proc//cmdline: add back the setproctitle() special case [Linux 4.19.64]

This Linux kernel change "/proc//cmdline: add back the setproctitle() special case" is included in the Linux 4.19.64 release. This change is authored by Linus Torvalds <torvalds [at] linux-foundation.org> on Sat Jul 13 14:27:14 2019 -0700. The commit for this change in Linux stable tree is 5469534 (patch) which is from upstream commit d26d0cd. The same Linux upstream change may have been applied to various maintained Linux releases and you can find all Linux releases containing changes from upstream d26d0cd.

/proc/<pid>/cmdline: add back the setproctitle() special case

commit d26d0cd97c88eb1a5704b42e41ab443406807810 upstream.

This makes the setproctitle() special case very explicit indeed, and
handles it with a separate helper function entirely.  In the process, it
re-instates the original semantics of simply stopping at the first NUL
character when the original last NUL character is no longer there.

[ The original semantics can still be seen in mm/util.c: get_cmdline()
  that is limited to a fixed-size buffer ]

This makes the logic about when we use the string lengths etc much more
obvious, and makes it easier to see what we do and what the two very
different cases are.

Note that even when we allow walking past the end of the argument array
(because the setproctitle() might have overwritten and overflowed the
original argv[] strings), we only allow it when it overflows into the
environment region if it is immediately adjacent.

[ Fixed for missing 'count' checks noted by Alexey Izbyshev ]

Link: https://lore.kernel.org/lkml/alpine.LNX.2.21.1904052326230.3249@kich.toxcorp.com/
Fixes: 5ab827189965 ("fs/proc: simplify and clarify get_mm_cmdline() function")
Cc: Jakub Jankowski <shasta@toxcorp.com>
Cc: Alexey Dobriyan <adobriyan@gmail.com>
Cc: Alexey Izbyshev <izbyshev@ispras.ru>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

There are 81 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 fs/proc/base.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 77 insertions(+), 4 deletions(-)

diff --git a/fs/proc/base.c b/fs/proc/base.c
index 2454740..3b9b726 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -205,12 +205,53 @@ static int proc_root_link(struct dentry *dentry, struct path *path)
    return result;
 }

+/*
+ * If the user used setproctitle(), we just get the string from
+ * user space at arg_start, and limit it to a maximum of one page.
+ */
+static ssize_t get_mm_proctitle(struct mm_struct *mm, char __user *buf,
+               size_t count, unsigned long pos,
+               unsigned long arg_start)
+{
+   char *page;
+   int ret, got;
+
+   if (pos >= PAGE_SIZE)
+       return 0;
+
+   page = (char *)__get_free_page(GFP_KERNEL);
+   if (!page)
+       return -ENOMEM;
+
+   ret = 0;
+   got = access_remote_vm(mm, arg_start, page, PAGE_SIZE, FOLL_ANON);
+   if (got > 0) {
+       int len = strnlen(page, got);
+
+       /* Include the NUL character if it was found */
+       if (len < got)
+           len++;
+
+       if (len > pos) {
+           len -= pos;
+           if (len > count)
+               len = count;
+           len -= copy_to_user(buf, page+pos, len);
+           if (!len)
+               len = -EFAULT;
+           ret = len;
+       }
+   }
+   free_page((unsigned long)page);
+   return ret;
+}
+
 static ssize_t get_mm_cmdline(struct mm_struct *mm, char __user *buf,
                  size_t count, loff_t *ppos)
 {
-   unsigned long arg_start, arg_end;
+   unsigned long arg_start, arg_end, env_start, env_end;
    unsigned long pos, len;
-   char *page;
+   char *page, c;

    /* Check if process spawned far enough to have cmdline. */
    if (!mm->env_end)
@@ -219,14 +260,46 @@ static ssize_t get_mm_cmdline(struct mm_struct *mm, char __user *buf,
    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;
    spin_unlock(&mm->arg_lock);

    if (arg_start >= arg_end)
        return 0;

+   /*
+    * We allow setproctitle() to overwrite the argument
+    * strings, and overflow past the original end. But
+    * only when it overflows into the environment area.
+    */
+   if (env_start != arg_end || env_end < env_start)
+       env_start = env_end = arg_end;
+   len = env_end - arg_start;
+
    /* We're not going to care if "*ppos" has high bits set */
-   /* .. but we do check the result is in the proper range */
-   pos = arg_start + *ppos;
+   pos = *ppos;
+   if (pos >= len)
+       return 0;
+   if (count > len - pos)
+       count = len - pos;
+   if (!count)
+       return 0;
+
+   /*
+    * Magical special case: if the argv[] end byte is not
+    * zero, the user has overwritten it with setproctitle(3).
+    *
+    * Possible future enhancement: do this only once when
+    * pos is 0, and set a flag in the 'struct file'.
+    */
+   if (access_remote_vm(mm, arg_end-1, &c, 1, FOLL_ANON) == 1 && c)
+       return get_mm_proctitle(mm, buf, count, pos, arg_start);
+
+   /*
+    * For the non-setproctitle() case we limit things strictly
+    * to the [arg_start, arg_end[ range.
+    */
+   pos += arg_start;
    if (pos < arg_start || pos >= arg_end)
        return 0;
    if (count > arg_end - pos)

Leave a Reply

Your email address will not be published. Required fields are marked *