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 /kernel/events | |
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>
Diffstat (limited to 'kernel/events')
-rw-r--r-- | kernel/events/uprobes.c | 58 |
1 files changed, 48 insertions, 10 deletions
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. |