diff options
| author | KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> | 2010-02-02 16:44:05 -0500 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2010-02-02 21:11:22 -0500 |
| commit | 325fda71d0badc1073dc59f12a948f24ff05796a (patch) | |
| tree | b143817aba0e8c0d9b4b689bde32fa893a9fb559 | |
| parent | 931e80e4b3263db75c8e34f078d22f11bbabd3a3 (diff) | |
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>
Cc: <stable@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
| -rw-r--r-- | drivers/char/mem.c | 28 |
1 files changed, 18 insertions, 10 deletions
diff --git a/drivers/char/mem.c b/drivers/char/mem.c index be832b6f8279..1fd4b110d815 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c | |||
| @@ -395,6 +395,7 @@ static ssize_t read_kmem(struct file *file, char __user *buf, | |||
| 395 | unsigned long p = *ppos; | 395 | unsigned long p = *ppos; |
| 396 | ssize_t low_count, read, sz; | 396 | ssize_t low_count, read, sz; |
| 397 | char * kbuf; /* k-addr because vread() takes vmlist_lock rwlock */ | 397 | char * kbuf; /* k-addr because vread() takes vmlist_lock rwlock */ |
| 398 | int err = 0; | ||
| 398 | 399 | ||
| 399 | read = 0; | 400 | read = 0; |
| 400 | if (p < (unsigned long) high_memory) { | 401 | if (p < (unsigned long) high_memory) { |
| @@ -441,12 +442,16 @@ static ssize_t read_kmem(struct file *file, char __user *buf, | |||
| 441 | return -ENOMEM; | 442 | return -ENOMEM; |
| 442 | while (count > 0) { | 443 | while (count > 0) { |
| 443 | sz = size_inside_page(p, count); | 444 | sz = size_inside_page(p, count); |
| 445 | if (!is_vmalloc_or_module_addr((void *)p)) { | ||
| 446 | err = -ENXIO; | ||
| 447 | break; | ||
| 448 | } | ||
| 444 | sz = vread(kbuf, (char *)p, sz); | 449 | sz = vread(kbuf, (char *)p, sz); |
| 445 | if (!sz) | 450 | if (!sz) |
| 446 | break; | 451 | break; |
| 447 | if (copy_to_user(buf, kbuf, sz)) { | 452 | if (copy_to_user(buf, kbuf, sz)) { |
| 448 | free_page((unsigned long)kbuf); | 453 | err = -EFAULT; |
| 449 | return -EFAULT; | 454 | break; |
| 450 | } | 455 | } |
| 451 | count -= sz; | 456 | count -= sz; |
| 452 | buf += sz; | 457 | buf += sz; |
| @@ -455,8 +460,8 @@ static ssize_t read_kmem(struct file *file, char __user *buf, | |||
| 455 | } | 460 | } |
| 456 | free_page((unsigned long)kbuf); | 461 | free_page((unsigned long)kbuf); |
| 457 | } | 462 | } |
| 458 | *ppos = p; | 463 | *ppos = p; |
| 459 | return read; | 464 | return read ? read : err; |
| 460 | } | 465 | } |
| 461 | 466 | ||
| 462 | 467 | ||
| @@ -520,6 +525,7 @@ static ssize_t write_kmem(struct file * file, const char __user * buf, | |||
| 520 | ssize_t wrote = 0; | 525 | ssize_t wrote = 0; |
| 521 | ssize_t virtr = 0; | 526 | ssize_t virtr = 0; |
| 522 | char * kbuf; /* k-addr because vwrite() takes vmlist_lock rwlock */ | 527 | char * kbuf; /* k-addr because vwrite() takes vmlist_lock rwlock */ |
| 528 | int err = 0; | ||
| 523 | 529 | ||
| 524 | if (p < (unsigned long) high_memory) { | 530 | if (p < (unsigned long) high_memory) { |
| 525 | unsigned long to_write = min_t(unsigned long, count, | 531 | unsigned long to_write = min_t(unsigned long, count, |
| @@ -540,12 +546,14 @@ static ssize_t write_kmem(struct file * file, const char __user * buf, | |||
| 540 | unsigned long sz = size_inside_page(p, count); | 546 | unsigned long sz = size_inside_page(p, count); |
| 541 | unsigned long n; | 547 | unsigned long n; |
| 542 | 548 | ||
| 549 | if (!is_vmalloc_or_module_addr((void *)p)) { | ||
| 550 | err = -ENXIO; | ||
| 551 | break; | ||
| 552 | } | ||
| 543 | n = copy_from_user(kbuf, buf, sz); | 553 | n = copy_from_user(kbuf, buf, sz); |
| 544 | if (n) { | 554 | if (n) { |
| 545 | if (wrote + virtr) | 555 | err = -EFAULT; |
| 546 | break; | 556 | break; |
| 547 | free_page((unsigned long)kbuf); | ||
| 548 | return -EFAULT; | ||
| 549 | } | 557 | } |
| 550 | sz = vwrite(kbuf, (char *)p, sz); | 558 | sz = vwrite(kbuf, (char *)p, sz); |
| 551 | count -= sz; | 559 | count -= sz; |
| @@ -556,8 +564,8 @@ static ssize_t write_kmem(struct file * file, const char __user * buf, | |||
| 556 | free_page((unsigned long)kbuf); | 564 | free_page((unsigned long)kbuf); |
| 557 | } | 565 | } |
| 558 | 566 | ||
| 559 | *ppos = p; | 567 | *ppos = p; |
| 560 | return virtr + wrote; | 568 | return virtr + wrote ? : err; |
| 561 | } | 569 | } |
| 562 | #endif | 570 | #endif |
| 563 | 571 | ||
