proc: fix PAGE_SIZE limit of /proc/$PID/cmdline
PATCH 1/1 proc: fix PAGE_SIZE limit of /proc/$PID/cmdline
/proc/$PID/cmdline truncates output at PAGE_SIZE. It is easy to see with
$ cat /proc/self/cmdline $(seq 1037) 2>/dev/null
However, command line size was never limited to PAGE_SIZE but to 128 KB and relatively recently limitation was removed altogether.
seq file interface is not OK, because it kmalloc's for whole output and open + read(, 1) + sleep will pin arbitrary amounts of kernel memory. To not do that, limit must be imposed which is incompatible with arbitrary sized command lines.
I apologize for hairy code, but this it direct consequence of command line layout in memory and hacks to support things like "init [3]".
The loops are "unrolled" otherwise it is either macros which hide control flow or functions with 7-8 arguments with equal line count.
There should be real setproctitle(2) or something.
---
fs/proc/base.c | 203 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 194 insertions(+), 9 deletions(-)
fs/proc/base.c +194 -9
@@ -196,18 +196,203 @@ static int proc_root_link(struct dentry *dentry, struct path *path)return result; } -static int proc_pid_cmdline (struct seq_file *m,struct pid_namespace *ns, -struct pid *pid,struct task_struct *task) +static ssize_t proc_pid_cmdline_read (struct file *file,char __user *buf, +size_t count,loff_t *pos) { +struct task_struct *tsk; +struct mm_struct *mm; +char *page; +unsigned long arg_start, arg_end, env_start, env_end; +unsigned long len1, len2, len; +unsigned long p; +char c; +ssize_t rv; + +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 ; +if (!mm->arg_end) { + rv =0 ; + goto out_mmput; + } + + page = (char *)__get_free_page (GFP_TEMPORARY ); +if (!page) { + rv = -ENOMEM ; + goto out_mmput; + } + +down_read (&mm->mmap_sem); + 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); + +BUG_ON (arg_start > arg_end); +BUG_ON (env_start > env_end); + + len1 = arg_end - arg_start; + len2 = env_end - env_start; +/* -* Rely on struct seq_operations::show() being called once -* per internal buffer allocation. See single_open(), traverse(). +* Inherently racy -- command line shares address space +* with code and data. */ -BUG_ON (m->size <PAGE_SIZE ); - m->count +=get_cmdline (task, m->buf,PAGE_SIZE ); -return 0 ; + rv =access_remote_vm (mm, arg_end -1 , &c,1 ,0 ); +if (rv <=0 ) + goto out_free_page; + + rv =0 ; + +if (c =='\0' ) { +/* Command line (set of strings) occupies whole ARGV. */ +if (len1 <= *pos) + goto out_free_page; + + p = arg_start + *pos; + len = len1 - *pos; +while (count >0 && len >0 ) { +unsigned int _count; +int nr_read; + + _count =min3 (count, len,PAGE_SIZE ); + nr_read =access_remote_vm (mm, p, page, _count,0 ); +if (nr_read <0 ) + rv = nr_read; +if (nr_read <=0 ) + goto out_free_page; + +if (copy_to_user (buf, page, nr_read)) { + rv = -EFAULT ; + goto out_free_page; + } + + p += nr_read; + len -= nr_read; + buf += nr_read; + count -= nr_read; + rv += nr_read; + } + }else { +/* +* Command line (1 string) occupies ARGV and maybe +* extends into ENVP. +*/ +if (len1 + len2 <= *pos) + goto skip_argv_envp; +if (len1 <= *pos) + goto skip_argv; + + p = arg_start + *pos; + len = len1 - *pos; +while (count >0 && len >0 ) { +unsigned int _count, l; +int nr_read; +bool final; + + _count =min3 (count, len,PAGE_SIZE ); + nr_read =access_remote_vm (mm, p, page, _count,0 ); +if (nr_read <0 ) + rv = nr_read; +if (nr_read <=0 ) + goto out_free_page; + +/* +* Command line can be shorter than whole ARGV +* even if last "marker" byte says it is not. +*/ + final = false; + l =strnlen (page, nr_read); +if (l < nr_read) { + nr_read = l; + final = true; + } + +if (copy_to_user (buf, page, nr_read)) { + rv = -EFAULT ; + goto out_free_page; + } + + p += nr_read; + len -= nr_read; + buf += nr_read; + count -= nr_read; + rv += nr_read; + +if (final) + goto out_free_page; + } +skip_argv: +/* +* Command line (1 string) occupies ARGV and +* extends into ENVP. +*/ +if (len1 <= *pos) { + p = env_start + *pos - len1; + len = len1 + len2 - *pos; + }else { + p = env_start; + len = len2; + } +while (count >0 && len >0 ) { +unsigned int _count, l; +int nr_read; +bool final; + + _count =min3 (count, len,PAGE_SIZE ); + nr_read =access_remote_vm (mm, p, page, _count,0 ); +if (nr_read <0 ) + rv = nr_read; +if (nr_read <=0 ) + goto out_free_page; + +/* Find EOS. */ + final = false; + l =strnlen (page, nr_read); +if (l < nr_read) { + nr_read = l; + final = true; + } + +if (copy_to_user (buf, page, nr_read)) { + rv = -EFAULT ; + goto out_free_page; + } + + p += nr_read; + len -= nr_read; + buf += nr_read; + count -= nr_read; + rv += nr_read; + +if (final) + goto out_free_page; + } +skip_argv_envp: + ; + } + +out_free_page: +free_page ((unsigned long )page); +out_mmput: +mmput (mm); +if (rv >0 ) + *pos += rv; +return rv; } +static const struct file_operations proc_pid_cmdline_ops = { + .read = proc_pid_cmdline_read, + .llseek = generic_file_llseek, +}; +static int proc_pid_auxv (struct seq_file *m,struct pid_namespace *ns,struct pid *pid,struct task_struct *task) {
@@ -2560,7 +2745,7 @@ static const struct pid_entry tgid_base_stuff[] = {#ifdef CONFIG_HAVE_ARCH_TRACEHOOK ONE ("syscall" ,S_IRUSR , proc_pid_syscall), #endif -ONE ("cmdline" ,S_IRUGO , proc_pid_cmdline), +REG ("cmdline" ,S_IRUGO , proc_pid_cmdline_ops),ONE ("stat" ,S_IRUGO , proc_tgid_stat),ONE ("statm" ,S_IRUGO , proc_pid_statm), REG("maps", S_IRUGO, proc_pid_maps_operations),
@@ -2906,7 +3091,7 @@ static const struct pid_entry tid_base_stuff[] = {#ifdef CONFIG_HAVE_ARCH_TRACEHOOK ONE ("syscall" ,S_IRUSR , proc_pid_syscall), #endif -ONE ("cmdline" ,S_IRUGO , proc_pid_cmdline), +REG ("cmdline" ,S_IRUGO , proc_pid_cmdline_ops),ONE ("stat" ,S_IRUGO , proc_tid_stat),ONE ("statm" ,S_IRUGO , proc_pid_statm), REG("maps", S_IRUGO, proc_tid_maps_operations),
Quoted 18 lines · On Fri, 20 Mar 2015 20:51:47 +0300 Alexey Dobriyan <[email protected]> wrote:
/proc/$PID/cmdlinetruncates output atPAGE_SIZE. It is easy to see with$ cat /proc/self/cmdline $(seq 1037) 2>/dev/nullHowever, command line size was never limited to
PAGE_SIZEbut to 128 KB and relatively recently limitation was removed altogether.seq file interface is not OK, because it kmalloc's for whole output and open +
read(, 1)+ sleep will pin arbitrary amounts of kernel memory. To not do that, limit must be imposed which is incompatible with arbitrary sized command lines.I apologize for hairy code, but this it direct consequence of command line layout in memory and hacks to support things like "init [3]".
The loops are "unrolled" otherwise it is either macros which hide control flow or functions with 7-8 arguments with equal line count.
Quoted 7 lines
There should be real setproctitle(2) or something.
...
fs/proc/base.c| 203 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---1 file changed, 194 insertions(+), 9 deletions(-)
I'm desperately trying to think of a reason to not merge this patch :(
I haven't seen anyone complain about the PAGE_SIZE limit - can't we pretend we didn't notice? Or tell 'em to switch to powerpc so they get 64k!
Quoted 8 lines · Andrew Morton
Quoted 1 line · On Fri, 20 Mar 2015 20:51:47 +0300 Alexey Dobriyan <[email protected]> wrote:
/proc/$PID/cmdlinetruncates output atPAGE_SIZE. It is easy to see withI'm desperately trying to think of a reason to not merge this patch :(
I haven't seen anyone complain about the
PAGE_SIZElimit - can't we pretend we didn't notice? Or tell 'em to switch to powerpc so they get 64k!
Well, people do notice the limitation
http://stackoverflow.com/questions/199130/how-do-i-increase-the-proc-pid-cmdline-4096-byte-limit
I think public interface should work 100% of the time.
setproctitle(2) can be added with documented 4KB limit.
Alexey