| From: Masami Hiramatsu <mhiramat@kernel.org> |
| Date: Sun, 24 Feb 2019 01:50:49 +0900 |
| Subject: x86/kprobes: Avoid kretprobe recursion bug |
| |
| commit b191fa96ea6dc00d331dcc28c1f7db5e075693a0 upstream. |
| |
| Avoid kretprobe recursion loop bg by setting a dummy |
| kprobes to current_kprobe per-CPU variable. |
| |
| This bug has been introduced with the asm-coded trampoline |
| code, since previously it used another kprobe for hooking |
| the function return placeholder (which only has a nop) and |
| trampoline handler was called from that kprobe. |
| |
| This revives the old lost kprobe again. |
| |
| With this fix, we don't see deadlock anymore. |
| |
| And you can see that all inner-called kretprobe are skipped. |
| |
| event_1 235 0 |
| event_2 19375 19612 |
| |
| The 1st column is recorded count and the 2nd is missed count. |
| Above shows (event_1 rec) + (event_2 rec) ~= (event_2 missed) |
| (some difference are here because the counter is racy) |
| |
| Reported-by: Andrea Righi <righi.andrea@gmail.com> |
| Tested-by: Andrea Righi <righi.andrea@gmail.com> |
| Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org> |
| Acked-by: Steven Rostedt <rostedt@goodmis.org> |
| Cc: Linus Torvalds <torvalds@linux-foundation.org> |
| Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com> |
| Cc: Peter Zijlstra <peterz@infradead.org> |
| Cc: Thomas Gleixner <tglx@linutronix.de> |
| Fixes: c9becf58d935 ("[PATCH] kretprobe: kretprobe-booster") |
| Link: http://lkml.kernel.org/r/155094064889.6137.972160690963039.stgit@devbox |
| Signed-off-by: Ingo Molnar <mingo@kernel.org> |
| [bwh: Backported to 3.16: adjust context] |
| Signed-off-by: Ben Hutchings <ben@decadent.org.uk> |
| --- |
| arch/x86/kernel/kprobes/core.c | 22 ++++++++++++++++++++-- |
| 1 file changed, 20 insertions(+), 2 deletions(-) |
| |
| --- a/arch/x86/kernel/kprobes/core.c |
| +++ b/arch/x86/kernel/kprobes/core.c |
| @@ -686,11 +686,16 @@ static void __used kretprobe_trampoline_ |
| NOKPROBE_SYMBOL(kretprobe_trampoline_holder); |
| NOKPROBE_SYMBOL(kretprobe_trampoline); |
| |
| +static struct kprobe kretprobe_kprobe = { |
| + .addr = (void *)kretprobe_trampoline, |
| +}; |
| + |
| /* |
| * Called from kretprobe_trampoline |
| */ |
| __visible __used void *trampoline_handler(struct pt_regs *regs) |
| { |
| + struct kprobe_ctlblk *kcb; |
| struct kretprobe_instance *ri = NULL; |
| struct hlist_head *head, empty_rp; |
| struct hlist_node *tmp; |
| @@ -700,6 +705,17 @@ __visible __used void *trampoline_handle |
| void *frame_pointer; |
| bool skipped = false; |
| |
| + preempt_disable(); |
| + |
| + /* |
| + * Set a dummy kprobe for avoiding kretprobe recursion. |
| + * Since kretprobe never run in kprobe handler, kprobe must not |
| + * be running at this point. |
| + */ |
| + kcb = get_kprobe_ctlblk(); |
| + __this_cpu_write(current_kprobe, &kretprobe_kprobe); |
| + kcb->kprobe_status = KPROBE_HIT_ACTIVE; |
| + |
| INIT_HLIST_HEAD(&empty_rp); |
| kretprobe_hash_lock(current, &head, &flags); |
| /* fixup registers */ |
| @@ -775,10 +791,9 @@ __visible __used void *trampoline_handle |
| orig_ret_address = (unsigned long)ri->ret_addr; |
| if (ri->rp && ri->rp->handler) { |
| __this_cpu_write(current_kprobe, &ri->rp->kp); |
| - get_kprobe_ctlblk()->kprobe_status = KPROBE_HIT_ACTIVE; |
| ri->ret_addr = correct_ret_addr; |
| ri->rp->handler(ri, regs); |
| - __this_cpu_write(current_kprobe, NULL); |
| + __this_cpu_write(current_kprobe, &kretprobe_kprobe); |
| } |
| |
| recycle_rp_inst(ri, &empty_rp); |
| @@ -794,6 +809,9 @@ __visible __used void *trampoline_handle |
| |
| kretprobe_hash_unlock(current, &flags); |
| |
| + __this_cpu_write(current_kprobe, NULL); |
| + preempt_enable(); |
| + |
| hlist_for_each_entry_safe(ri, tmp, &empty_rp, hlist) { |
| hlist_del(&ri->hlist); |
| kfree(ri); |