diff options
| author | Oleg Nesterov <oleg@redhat.com> | 2012-12-29 11:49:11 -0500 |
|---|---|---|
| committer | Oleg Nesterov <oleg@redhat.com> | 2013-02-08 11:47:11 -0500 |
| commit | da1816b1caeccdff04531e763bb35d7caa3ed19f (patch) | |
| tree | bbf3b1eda3f969a5115770f0aa1081feafd871cb | |
| parent | 8a7f2fa0dea3b019500961b86d765e6fdd4bffb2 (diff) | |
uprobes: Teach handler_chain() to filter out the probed task
Currrently the are 2 problems with pre-filtering:
1. It is not possible to add/remove a task (mm) after uprobe_register()
2. A forked child inherits all breakpoints and uprobe_consumer can not
control this.
This patch does the first step to improve the filtering. handler_chain()
removes the breakpoints installed by this uprobe from current->mm if all
handlers return UPROBE_HANDLER_REMOVE.
Note that handler_chain() relies on ->register_rwsem to avoid the race
with uprobe_register/unregister which can add/del a consumer, or even
remove and then insert the new uprobe at the same address.
Perhaps we will add uprobe_apply_mm(uprobe, mm, is_register) and teach
copy_mm() to do filter(UPROBE_FILTER_FORK), but I think this change makes
sense anyway.
Note: instead of checking the retcode from uc->handler, we could add
uc->filter(UPROBE_FILTER_BPHIT). But I think this is not optimal to
call 2 hooks in a row. This buys nothing, and if handler/filter do
something nontrivial they will probably do the same work twice.
Signed-off-by: Oleg Nesterov <oleg@redhat.com>
Acked-by: Srikar Dronamraju <srikar@linux.vnet.ibm.com>
| -rw-r--r-- | include/linux/uprobes.h | 3 | ||||
| -rw-r--r-- | kernel/events/uprobes.c | 58 |
2 files changed, 51 insertions, 10 deletions
diff --git a/include/linux/uprobes.h b/include/linux/uprobes.h index c2df6934fdc6..95d0002efda5 100644 --- a/include/linux/uprobes.h +++ b/include/linux/uprobes.h | |||
| @@ -35,6 +35,9 @@ struct inode; | |||
| 35 | # include <asm/uprobes.h> | 35 | # include <asm/uprobes.h> |
| 36 | #endif | 36 | #endif |
| 37 | 37 | ||
| 38 | #define UPROBE_HANDLER_REMOVE 1 | ||
| 39 | #define UPROBE_HANDLER_MASK 1 | ||
| 40 | |||
| 38 | enum uprobe_filter_ctx { | 41 | enum uprobe_filter_ctx { |
| 39 | UPROBE_FILTER_REGISTER, | 42 | UPROBE_FILTER_REGISTER, |
| 40 | UPROBE_FILTER_UNREGISTER, | 43 | UPROBE_FILTER_UNREGISTER, |
diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index c2737be3c4b8..04c104ad9522 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c | |||
| @@ -440,16 +440,6 @@ static struct uprobe *alloc_uprobe(struct inode *inode, loff_t offset) | |||
| 440 | return uprobe; | 440 | return uprobe; |
| 441 | } | 441 | } |
| 442 | 442 | ||
| 443 | static void handler_chain(struct uprobe *uprobe, struct pt_regs *regs) | ||
| 444 | { | ||
| 445 | struct uprobe_consumer *uc; | ||
| 446 | |||
| 447 | down_read(&uprobe->register_rwsem); | ||
| 448 | for (uc = uprobe->consumers; uc; uc = uc->next) | ||
| 449 | uc->handler(uc, regs); | ||
| 450 | up_read(&uprobe->register_rwsem); | ||
| 451 | } | ||
| 452 | |||
| 453 | static void consumer_add(struct uprobe *uprobe, struct uprobe_consumer *uc) | 443 | static void consumer_add(struct uprobe *uprobe, struct uprobe_consumer *uc) |
| 454 | { | 444 | { |
| 455 | down_write(&uprobe->consumer_rwsem); | 445 | down_write(&uprobe->consumer_rwsem); |
| @@ -882,6 +872,33 @@ void uprobe_unregister(struct inode *inode, loff_t offset, struct uprobe_consume | |||
| 882 | put_uprobe(uprobe); | 872 | put_uprobe(uprobe); |
| 883 | } | 873 | } |
| 884 | 874 | ||
| 875 | static int unapply_uprobe(struct uprobe *uprobe, struct mm_struct *mm) | ||
| 876 | { | ||
| 877 | struct vm_area_struct *vma; | ||
| 878 | int err = 0; | ||
| 879 | |||
| 880 | down_read(&mm->mmap_sem); | ||
| 881 | for (vma = mm->mmap; vma; vma = vma->vm_next) { | ||
| 882 | unsigned long vaddr; | ||
| 883 | loff_t offset; | ||
| 884 | |||
| 885 | if (!valid_vma(vma, false) || | ||
| 886 | vma->vm_file->f_mapping->host != uprobe->inode) | ||
| 887 | continue; | ||
| 888 | |||
| 889 | offset = (loff_t)vma->vm_pgoff << PAGE_SHIFT; | ||
| 890 | if (uprobe->offset < offset || | ||
| 891 | uprobe->offset >= offset + vma->vm_end - vma->vm_start) | ||
| 892 | continue; | ||
| 893 | |||
| 894 | vaddr = offset_to_vaddr(vma, uprobe->offset); | ||
| 895 | err |= remove_breakpoint(uprobe, mm, vaddr); | ||
| 896 | } | ||
| 897 | up_read(&mm->mmap_sem); | ||
| 898 | |||
| 899 | return err; | ||
| 900 | } | ||
| 901 | |||
| 885 | static struct rb_node * | 902 | static struct rb_node * |
| 886 | find_node_in_range(struct inode *inode, loff_t min, loff_t max) | 903 | find_node_in_range(struct inode *inode, loff_t min, loff_t max) |
| 887 | { | 904 | { |
| @@ -1435,6 +1452,27 @@ static struct uprobe *find_active_uprobe(unsigned long bp_vaddr, int *is_swbp) | |||
| 1435 | return uprobe; | 1452 | return uprobe; |
| 1436 | } | 1453 | } |
| 1437 | 1454 | ||
| 1455 | static void handler_chain(struct uprobe *uprobe, struct pt_regs *regs) | ||
| 1456 | { | ||
| 1457 | struct uprobe_consumer *uc; | ||
| 1458 | int remove = UPROBE_HANDLER_REMOVE; | ||
| 1459 | |||
| 1460 | down_read(&uprobe->register_rwsem); | ||
| 1461 | for (uc = uprobe->consumers; uc; uc = uc->next) { | ||
| 1462 | int rc = uc->handler(uc, regs); | ||
| 1463 | |||
| 1464 | WARN(rc & ~UPROBE_HANDLER_MASK, | ||
| 1465 | "bad rc=0x%x from %pf()\n", rc, uc->handler); | ||
| 1466 | remove &= rc; | ||
| 1467 | } | ||
| 1468 | |||
| 1469 | if (remove && uprobe->consumers) { | ||
| 1470 | WARN_ON(!uprobe_is_active(uprobe)); | ||
| 1471 | unapply_uprobe(uprobe, current->mm); | ||
| 1472 | } | ||
| 1473 | up_read(&uprobe->register_rwsem); | ||
| 1474 | } | ||
| 1475 | |||
| 1438 | /* | 1476 | /* |
| 1439 | * Run handler and ask thread to singlestep. | 1477 | * Run handler and ask thread to singlestep. |
| 1440 | * Ensure all non-fatal signals cannot interrupt thread while it singlesteps. | 1478 | * Ensure all non-fatal signals cannot interrupt thread while it singlesteps. |
