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; + unsigned long pos, len; + char *page; - BUG_ON(*pos < 0); - - 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; + if (env_start != arg_end || env_start >= env_end) + env_start = env_end = arg_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; + /* 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; + + /* .. 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; + } + + got -= copy_to_user(buf, page, got); + if (unlikely(!got)) { + if (!len) + len = -EFAULT; + break; + } + pos += got; + buf += got; + len += got; + count -= got; } - { - loff_t pos1 = *pos; - unsigned int i; - - i = 0; - while (i < 2 && pos1 >= cmdline[i].len) { - pos1 -= cmdline[i].len; - i++; - } - 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++; - } - } - -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 = {