aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOleg Nesterov <oleg@redhat.com>2013-10-13 15:18:44 -0400
committerOleg Nesterov <oleg@redhat.com>2013-10-29 13:02:54 -0400
commitaa59c53fd4599c91ccf9629af0c2777b89929076 (patch)
treee6bae950990def50e9d0bd2c8a8eb7728d2c5644
parent248d3a7b2f100078c5f6878351177859380582e9 (diff)
uprobes: Change uprobe_copy_process() to dup xol_area
This finally fixes the serious bug in uretprobes: a forked child crashes if the parent called fork() with the pending ret probe. Trivial test-case: # perf probe -x /lib/libc.so.6 __fork%return # perf record -e probe_libc:__fork perl -le 'fork || print "OK"' (the child doesn't print "OK", it is killed by SIGSEGV) If the child returns from the probed function it actually returns to trampoline_vaddr, because it got the copy of parent's stack mangled by prepare_uretprobe() when the parent entered this func. It crashes because a) this address is not mapped and b) until the previous change it doesn't have the proper->return_instances info. This means that uprobe_copy_process() has to create xol_area which has the trampoline slot, and its vaddr should be equal to parent's xol_area->vaddr. Unfortunately, uprobe_copy_process() can not simply do __create_xol_area(child, xol_area->vaddr). This could actually work but perf_event_mmap() doesn't expect the usage of foreign ->mm. So we offload this to task_work_run(), and pass the argument via not yet used utask->vaddr. We know that this vaddr is fine for install_special_mapping(), the necessary hole was recently "created" by dup_mmap() which skips the parent's VM_DONTCOPY area, and nobody else could use the new mm. Unfortunately, this also means that we can not handle the errors properly, we obviously can not abort the already completed fork(). So we simply print the warning if GFP_KERNEL allocation (the only possible reason) fails. Reported-by: Martin Cermak <mcermak@redhat.com> Reported-by: David Smith <dsmith@redhat.com> Signed-off-by: Oleg Nesterov <oleg@redhat.com> Acked-by: Srikar Dronamraju <srikar@linux.vnet.ibm.com>
-rw-r--r--kernel/events/uprobes.c28
1 files changed, 28 insertions, 0 deletions
diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c
index 1c6cda68a555..9f282e14925d 100644
--- a/kernel/events/uprobes.c
+++ b/kernel/events/uprobes.c
@@ -35,6 +35,7 @@
35#include <linux/kdebug.h> /* notifier mechanism */ 35#include <linux/kdebug.h> /* notifier mechanism */
36#include "../../mm/internal.h" /* munlock_vma_page */ 36#include "../../mm/internal.h" /* munlock_vma_page */
37#include <linux/percpu-rwsem.h> 37#include <linux/percpu-rwsem.h>
38#include <linux/task_work.h>
38 39
39#include <linux/uprobes.h> 40#include <linux/uprobes.h>
40 41
@@ -1400,6 +1401,17 @@ static void uprobe_warn(struct task_struct *t, const char *msg)
1400 current->comm, current->pid, msg); 1401 current->comm, current->pid, msg);
1401} 1402}
1402 1403
1404static void dup_xol_work(struct callback_head *work)
1405{
1406 kfree(work);
1407
1408 if (current->flags & PF_EXITING)
1409 return;
1410
1411 if (!__create_xol_area(current->utask->vaddr))
1412 uprobe_warn(current, "dup xol area");
1413}
1414
1403/* 1415/*
1404 * Called in context of a new clone/fork from copy_process. 1416 * Called in context of a new clone/fork from copy_process.
1405 */ 1417 */
@@ -1407,6 +1419,8 @@ void uprobe_copy_process(struct task_struct *t)
1407{ 1419{
1408 struct uprobe_task *utask = current->utask; 1420 struct uprobe_task *utask = current->utask;
1409 struct mm_struct *mm = current->mm; 1421 struct mm_struct *mm = current->mm;
1422 struct callback_head *work;
1423 struct xol_area *area;
1410 1424
1411 t->utask = NULL; 1425 t->utask = NULL;
1412 1426
@@ -1415,6 +1429,20 @@ void uprobe_copy_process(struct task_struct *t)
1415 1429
1416 if (dup_utask(t, utask)) 1430 if (dup_utask(t, utask))
1417 return uprobe_warn(t, "dup ret instances"); 1431 return uprobe_warn(t, "dup ret instances");
1432
1433 /* The task can fork() after dup_xol_work() fails */
1434 area = mm->uprobes_state.xol_area;
1435 if (!area)
1436 return uprobe_warn(t, "dup xol area");
1437
1438 /* TODO: move it into the union in uprobe_task */
1439 work = kmalloc(sizeof(*work), GFP_KERNEL);
1440 if (!work)
1441 return uprobe_warn(t, "dup xol area");
1442
1443 utask->vaddr = area->vaddr;
1444 init_task_work(work, dup_xol_work);
1445 task_work_add(t, work, true);
1418} 1446}
1419 1447
1420/* 1448/*