diff options
author | Oleg Nesterov <oleg@redhat.com> | 2013-11-07 13:41:57 -0500 |
---|---|---|
committer | Oleg Nesterov <oleg@redhat.com> | 2013-11-09 11:05:43 -0500 |
commit | 2ded0980a6e4ae96bdd84bda66c7240967d86f3c (patch) | |
tree | 126d757f23d6fa47c04ed695eccb2b8b9e0bb176 | |
parent | 70d7f98722a7a1df1a55d6a92d0ce959c7aba9fd (diff) |
uprobes: Fix the memory out of bound overwrite in copy_insn()
1. copy_insn() doesn't look very nice, all calculations are
confusing and it is not immediately clear why do we read
the 2nd page first.
2. The usage of inode->i_size is wrong on 32-bit machines.
3. "Instruction at end of binary" logic is simply wrong, it
doesn't handle the case when uprobe->offset > inode->i_size.
In this case "bytes" overflows, and __copy_insn() writes to
the memory outside of uprobe->arch.insn.
Yes, uprobe_register() checks i_size_read(), but this file
can be truncated after that. All i_size checks are racy, we
do this only to catch the obvious mistakes.
Change copy_insn() to call __copy_insn() in a loop, simplify
and fix the bytes/nbytes calculations.
Note: we do not care if we read extra bytes after inode->i_size
if we got the valid page. This is fine because the task gets the
same page after page-fault, and arch_uprobe_analyze_insn() can't
know how many bytes were actually read anyway.
Signed-off-by: Oleg Nesterov <oleg@redhat.com>
-rw-r--r-- | kernel/events/uprobes.c | 43 |
1 files changed, 21 insertions, 22 deletions
diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index 5e5695038d2d..24b7d6ca871b 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c | |||
@@ -504,9 +504,8 @@ static bool consumer_del(struct uprobe *uprobe, struct uprobe_consumer *uc) | |||
504 | return ret; | 504 | return ret; |
505 | } | 505 | } |
506 | 506 | ||
507 | static int | 507 | static int __copy_insn(struct address_space *mapping, struct file *filp, |
508 | __copy_insn(struct address_space *mapping, struct file *filp, char *insn, | 508 | void *insn, int nbytes, loff_t offset) |
509 | unsigned long nbytes, loff_t offset) | ||
510 | { | 509 | { |
511 | struct page *page; | 510 | struct page *page; |
512 | 511 | ||
@@ -528,28 +527,28 @@ __copy_insn(struct address_space *mapping, struct file *filp, char *insn, | |||
528 | 527 | ||
529 | static int copy_insn(struct uprobe *uprobe, struct file *filp) | 528 | static int copy_insn(struct uprobe *uprobe, struct file *filp) |
530 | { | 529 | { |
531 | struct address_space *mapping; | 530 | struct address_space *mapping = uprobe->inode->i_mapping; |
532 | unsigned long nbytes; | 531 | loff_t offs = uprobe->offset; |
533 | int bytes; | 532 | void *insn = uprobe->arch.insn; |
534 | 533 | int size = MAX_UINSN_BYTES; | |
535 | nbytes = PAGE_SIZE - (uprobe->offset & ~PAGE_MASK); | 534 | int len, err = -EIO; |
536 | mapping = uprobe->inode->i_mapping; | ||
537 | 535 | ||
538 | /* Instruction at end of binary; copy only available bytes */ | 536 | /* Copy only available bytes, -EIO if nothing was read */ |
539 | if (uprobe->offset + MAX_UINSN_BYTES > uprobe->inode->i_size) | 537 | do { |
540 | bytes = uprobe->inode->i_size - uprobe->offset; | 538 | if (offs >= i_size_read(uprobe->inode)) |
541 | else | 539 | break; |
542 | bytes = MAX_UINSN_BYTES; | ||
543 | 540 | ||
544 | /* Instruction at the page-boundary; copy bytes in second page */ | 541 | len = min_t(int, size, PAGE_SIZE - (offs & ~PAGE_MASK)); |
545 | if (nbytes < bytes) { | 542 | err = __copy_insn(mapping, filp, insn, len, offs); |
546 | int err = __copy_insn(mapping, filp, uprobe->arch.insn + nbytes, | ||
547 | bytes - nbytes, uprobe->offset + nbytes); | ||
548 | if (err) | 543 | if (err) |
549 | return err; | 544 | break; |
550 | bytes = nbytes; | 545 | |
551 | } | 546 | insn += len; |
552 | return __copy_insn(mapping, filp, uprobe->arch.insn, bytes, uprobe->offset); | 547 | offs += len; |
548 | size -= len; | ||
549 | } while (size); | ||
550 | |||
551 | return err; | ||
553 | } | 552 | } |
554 | 553 | ||
555 | static int prepare_uprobe(struct uprobe *uprobe, struct file *file, | 554 | static int prepare_uprobe(struct uprobe *uprobe, struct file *file, |