aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOleg Nesterov <oleg@redhat.com>2013-10-16 13:39:37 -0400
committerOleg Nesterov <oleg@redhat.com>2013-10-29 13:02:55 -0400
commit3ab679661721b1ec2aaad99a801870ed59ab1110 (patch)
treef651a01eb98dd5454d433da9ae35579242a2cb36
parentaa59c53fd4599c91ccf9629af0c2777b89929076 (diff)
uprobes: Teach uprobe_copy_process() to handle CLONE_VFORK
uprobe_copy_process() does nothing if the child shares ->mm with the forking process, but there is a special case: CLONE_VFORK. In this case it would be more correct to do dup_utask() but avoid dup_xol(). This is not that important, the child should not unwind its stack too much, this can corrupt the parent's stack, but at least we need this to allow to ret-probe __vfork() itself. Note: in theory, it would be better to check task_pt_regs(p)->sp instead of CLONE_VFORK, we need to dup_utask() if and only if the child can return from the function called by the parent. But this needs the arch-dependant helper, and I think that nobody actually does clone(same_stack, CLONE_VM). Reported-by: Martin Cermak <mcermak@redhat.com> Reported-by: David Smith <dsmith@redhat.com> Signed-off-by: Oleg Nesterov <oleg@redhat.com>
-rw-r--r--include/linux/uprobes.h4
-rw-r--r--kernel/events/uprobes.c10
-rw-r--r--kernel/fork.c2
3 files changed, 11 insertions, 5 deletions
diff --git a/include/linux/uprobes.h b/include/linux/uprobes.h
index e6fba627ea45..9e0d5a6fe7a8 100644
--- a/include/linux/uprobes.h
+++ b/include/linux/uprobes.h
@@ -117,7 +117,7 @@ extern void uprobe_start_dup_mmap(void);
117extern void uprobe_end_dup_mmap(void); 117extern void uprobe_end_dup_mmap(void);
118extern void uprobe_dup_mmap(struct mm_struct *oldmm, struct mm_struct *newmm); 118extern void uprobe_dup_mmap(struct mm_struct *oldmm, struct mm_struct *newmm);
119extern void uprobe_free_utask(struct task_struct *t); 119extern void uprobe_free_utask(struct task_struct *t);
120extern void uprobe_copy_process(struct task_struct *t); 120extern void uprobe_copy_process(struct task_struct *t, unsigned long flags);
121extern unsigned long __weak uprobe_get_swbp_addr(struct pt_regs *regs); 121extern unsigned long __weak uprobe_get_swbp_addr(struct pt_regs *regs);
122extern int uprobe_post_sstep_notifier(struct pt_regs *regs); 122extern int uprobe_post_sstep_notifier(struct pt_regs *regs);
123extern int uprobe_pre_sstep_notifier(struct pt_regs *regs); 123extern int uprobe_pre_sstep_notifier(struct pt_regs *regs);
@@ -174,7 +174,7 @@ static inline unsigned long uprobe_get_swbp_addr(struct pt_regs *regs)
174static inline void uprobe_free_utask(struct task_struct *t) 174static inline void uprobe_free_utask(struct task_struct *t)
175{ 175{
176} 176}
177static inline void uprobe_copy_process(struct task_struct *t) 177static inline void uprobe_copy_process(struct task_struct *t, unsigned long flags)
178{ 178{
179} 179}
180static inline void uprobe_clear_state(struct mm_struct *mm) 180static inline void uprobe_clear_state(struct mm_struct *mm)
diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c
index 9f282e14925d..ae9e1d2ef256 100644
--- a/kernel/events/uprobes.c
+++ b/kernel/events/uprobes.c
@@ -1415,7 +1415,7 @@ static void dup_xol_work(struct callback_head *work)
1415/* 1415/*
1416 * Called in context of a new clone/fork from copy_process. 1416 * Called in context of a new clone/fork from copy_process.
1417 */ 1417 */
1418void uprobe_copy_process(struct task_struct *t) 1418void uprobe_copy_process(struct task_struct *t, unsigned long flags)
1419{ 1419{
1420 struct uprobe_task *utask = current->utask; 1420 struct uprobe_task *utask = current->utask;
1421 struct mm_struct *mm = current->mm; 1421 struct mm_struct *mm = current->mm;
@@ -1424,7 +1424,10 @@ void uprobe_copy_process(struct task_struct *t)
1424 1424
1425 t->utask = NULL; 1425 t->utask = NULL;
1426 1426
1427 if (mm == t->mm || !utask || !utask->return_instances) 1427 if (!utask || !utask->return_instances)
1428 return;
1429
1430 if (mm == t->mm && !(flags & CLONE_VFORK))
1428 return; 1431 return;
1429 1432
1430 if (dup_utask(t, utask)) 1433 if (dup_utask(t, utask))
@@ -1435,6 +1438,9 @@ void uprobe_copy_process(struct task_struct *t)
1435 if (!area) 1438 if (!area)
1436 return uprobe_warn(t, "dup xol area"); 1439 return uprobe_warn(t, "dup xol area");
1437 1440
1441 if (mm == t->mm)
1442 return;
1443
1438 /* TODO: move it into the union in uprobe_task */ 1444 /* TODO: move it into the union in uprobe_task */
1439 work = kmalloc(sizeof(*work), GFP_KERNEL); 1445 work = kmalloc(sizeof(*work), GFP_KERNEL);
1440 if (!work) 1446 if (!work)
diff --git a/kernel/fork.c b/kernel/fork.c
index d3603b81246b..8531609b6a82 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1489,7 +1489,7 @@ static struct task_struct *copy_process(unsigned long clone_flags,
1489 perf_event_fork(p); 1489 perf_event_fork(p);
1490 1490
1491 trace_task_newtask(p, clone_flags); 1491 trace_task_newtask(p, clone_flags);
1492 uprobe_copy_process(p); 1492 uprobe_copy_process(p, clone_flags);
1493 1493
1494 return p; 1494 return p;
1495 1495