diff options
author | Oleg Nesterov <oleg@redhat.com> | 2013-10-16 13:39:37 -0400 |
---|---|---|
committer | Oleg Nesterov <oleg@redhat.com> | 2013-10-29 13:02:55 -0400 |
commit | 3ab679661721b1ec2aaad99a801870ed59ab1110 (patch) | |
tree | f651a01eb98dd5454d433da9ae35579242a2cb36 | |
parent | aa59c53fd4599c91ccf9629af0c2777b89929076 (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.h | 4 | ||||
-rw-r--r-- | kernel/events/uprobes.c | 10 | ||||
-rw-r--r-- | kernel/fork.c | 2 |
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); | |||
117 | extern void uprobe_end_dup_mmap(void); | 117 | extern void uprobe_end_dup_mmap(void); |
118 | extern void uprobe_dup_mmap(struct mm_struct *oldmm, struct mm_struct *newmm); | 118 | extern void uprobe_dup_mmap(struct mm_struct *oldmm, struct mm_struct *newmm); |
119 | extern void uprobe_free_utask(struct task_struct *t); | 119 | extern void uprobe_free_utask(struct task_struct *t); |
120 | extern void uprobe_copy_process(struct task_struct *t); | 120 | extern void uprobe_copy_process(struct task_struct *t, unsigned long flags); |
121 | extern unsigned long __weak uprobe_get_swbp_addr(struct pt_regs *regs); | 121 | extern unsigned long __weak uprobe_get_swbp_addr(struct pt_regs *regs); |
122 | extern int uprobe_post_sstep_notifier(struct pt_regs *regs); | 122 | extern int uprobe_post_sstep_notifier(struct pt_regs *regs); |
123 | extern int uprobe_pre_sstep_notifier(struct pt_regs *regs); | 123 | extern 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) | |||
174 | static inline void uprobe_free_utask(struct task_struct *t) | 174 | static inline void uprobe_free_utask(struct task_struct *t) |
175 | { | 175 | { |
176 | } | 176 | } |
177 | static inline void uprobe_copy_process(struct task_struct *t) | 177 | static inline void uprobe_copy_process(struct task_struct *t, unsigned long flags) |
178 | { | 178 | { |
179 | } | 179 | } |
180 | static inline void uprobe_clear_state(struct mm_struct *mm) | 180 | static 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 | */ |
1418 | void uprobe_copy_process(struct task_struct *t) | 1418 | void 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 | ||