diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2018-06-09 15:31:35 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-06-09 15:31:35 -0700 |
commit | 3ca24ce9ff764bc27bceb9b2fd8ece74846c3fd3 (patch) | |
tree | e9b0d20cabfbc0a5a9a9a5b8aa4be447f840463f /fs/proc/base.c | |
parent | f72328d27f3bb117b7f707fae2ca20d03ba2796d (diff) | |
parent | 5ab8271899658042fabc5ae7e6a99066a210bc0e (diff) | |
download | linux-3ca24ce9ff764bc27bceb9b2fd8ece74846c3fd3.tar.bz2 |
Merge branch 'proc-cmdline'
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
Diffstat (limited to 'fs/proc/base.c')
-rw-r--r-- | fs/proc/base.c | 211 |
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 = { |