aboutsummaryrefslogtreecommitdiffstats
path: root/fs/proc
diff options
context:
space:
mode:
Diffstat (limited to 'fs/proc')
-rw-r--r--fs/proc/base.c211
1 files changed, 99 insertions, 112 deletions
diff --git a/fs/proc/base.c b/fs/proc/base.c
index 8358c5330c0b..4aa9ce5df02f 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -205,43 +205,16 @@ static int proc_root_link(struct dentry *dentry, struct path *path)
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;
spin_lock(&mm->arg_lock);
arg_start = mm->arg_start;
@@ -250,97 +223,111 @@ static ssize_t proc_pid_cmdline_read(struct file *file, char __user *buf,
env_end = mm->env_end;
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;
- {
- loff_t pos1 = *pos;
- unsigned int i;
+ /* We're not going to care if "*ppos" has high bits set */
+ pos = arg_start + *ppos;
+
+ /* .. 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 = {