devmem: check vmalloc address on kmem read/write
authorKAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
Fri, 5 Feb 2010 20:01:09 +0000 (15:01 -0500)
committerGreg Kroah-Hartman <gregkh@suse.de>
Tue, 23 Feb 2010 15:37:54 +0000 (07:37 -0800)
commit 325fda71d0badc1073dc59f12a948f24ff05796a

[ cebbert@redhat.com : backport to 2.6.32 ]

devmem: check vmalloc address on kmem read/write

Otherwise vmalloc_to_page() will BUG().

This also makes the kmem read/write implementation aligned with mem(4):
"References to nonexistent locations cause errors to be returned." Here we
return -ENXIO (inspired by Hugh) if no bytes have been transfered to/from
user space, otherwise return partial read/write results.

Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
Signed-off-by: Wu Fengguang <fengguang.wu@intel.com>
Cc: Greg Kroah-Hartman <gregkh@suse.de>
Cc: Hugh Dickins <hugh.dickins@tiscali.co.uk>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Chuck Ebbert <cebbert@redhat.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/char/mem.c

index 1ffb6dd3eae4201705656edac515288d3ff411a0..96b2586d2bec58aaa4568b0a7c908afda4be7337 100644 (file)
@@ -421,6 +421,7 @@ static ssize_t read_kmem(struct file *file, char __user *buf,
        unsigned long p = *ppos;
        ssize_t low_count, read, sz;
        char * kbuf; /* k-addr because vread() takes vmlist_lock rwlock */
+       int err = 0;
 
        read = 0;
        if (p < (unsigned long) high_memory) {
@@ -469,12 +470,16 @@ static ssize_t read_kmem(struct file *file, char __user *buf,
                while (count > 0) {
                        int len = size_inside_page(p, count);
 
+                       if (!is_vmalloc_or_module_addr((void *)p)) {
+                               err = -ENXIO;
+                               break;
+                       }
                        len = vread(kbuf, (char *)p, len);
                        if (!len)
                                break;
                        if (copy_to_user(buf, kbuf, len)) {
-                               free_page((unsigned long)kbuf);
-                               return -EFAULT;
+                               err = -EFAULT;
+                               break;
                        }
                        count -= len;
                        buf += len;
@@ -483,8 +488,8 @@ static ssize_t read_kmem(struct file *file, char __user *buf,
                }
                free_page((unsigned long)kbuf);
        }
-       *ppos = p;
-       return read;
+       *ppos = p;
+       return read ? read : err;
 }
 
 
@@ -553,6 +558,7 @@ static ssize_t write_kmem(struct file * file, const char __user * buf,
        ssize_t virtr = 0;
        ssize_t written;
        char * kbuf; /* k-addr because vwrite() takes vmlist_lock rwlock */
+       int err = 0;
 
        if (p < (unsigned long) high_memory) {
 
@@ -576,13 +582,15 @@ static ssize_t write_kmem(struct file * file, const char __user * buf,
                while (count > 0) {
                        int len = size_inside_page(p, count);
 
+                       if (!is_vmalloc_or_module_addr((void *)p)) {
+                               err = -ENXIO;
+                               break;
+                       }
                        if (len) {
                                written = copy_from_user(kbuf, buf, len);
                                if (written) {
-                                       if (wrote + virtr)
-                                               break;
-                                       free_page((unsigned long)kbuf);
-                                       return -EFAULT;
+                                       err = -EFAULT;
+                                       break;
                                }
                        }
                        len = vwrite(kbuf, (char *)p, len);
@@ -594,8 +602,8 @@ static ssize_t write_kmem(struct file * file, const char __user * buf,
                free_page((unsigned long)kbuf);
        }
 
-       *ppos = p;
-       return virtr + wrote;
+       *ppos = p;
+       return virtr + wrote ? : err;
 }
 #endif