Message ID | 162400004562.506599.7549585083316952768.stgit@devnote2 (mailing list archive) |
---|---|
State | Not Applicable |
Delegated to: | Netdev Maintainers |
Headers | show |
Series | kprobes: Fix stacktrace with kretprobes on x86 | expand |
Context | Check | Description |
---|---|---|
netdev/tree_selection | success | Not a local patch |
* Masami Hiramatsu <mhiramat@kernel.org> wrote: > In x86, kretprobe trampoline address on the stack frame will > be replaced with the real return address after returning from > trampoline_handler. Before fixing the return address, the real > return address can be found in the current->kretprobe_instances. > > However, since there is a window between updating the > current->kretprobe_instances and fixing the address on the stack, > if an interrupt caused at that timing and the interrupt handler > does stacktrace, it may fail to unwind because it can not get > the correct return address from current->kretprobe_instances. > > This will minimize that window by fixing the return address > right before updating current->kretprobe_instances. Is there still a window? I.e. is it "minimized" (to how big of a window?), or eliminated? > +void arch_kretprobe_fixup_return(struct pt_regs *regs, > + unsigned long correct_ret_addr) > +{ > + unsigned long *frame_pointer; > + > + frame_pointer = ((unsigned long *)®s->sp) + 1; > + > + /* Replace fake return address with real one. */ > + *frame_pointer = correct_ret_addr; Firstly, why does ®s->sp have to be forced to 'unsigned long *'? pt_regs::sp is 'unsigned long' on both 32-bit and 64-bit kernels AFAICS. Secondly, the new code modified by your patch now looks like this: frame_pointer = ((unsigned long *)®s->sp) + 1; + kretprobe_trampoline_handler(regs, frame_pointer); where: +void arch_kretprobe_fixup_return(struct pt_regs *regs, + unsigned long correct_ret_addr) +{ + unsigned long *frame_pointer; + + frame_pointer = ((unsigned long *)®s->sp) + 1; + + /* Replace fake return address with real one. */ + *frame_pointer = correct_ret_addr; +} So we first do: frame_pointer = ((unsigned long *)®s->sp) + 1; ... and pass that in to arch_kretprobe_fixup_return() as 'correct_ret_addr', which does: + frame_pointer = ((unsigned long *)®s->sp) + 1; + *frame_pointer = correct_ret_addr; ... which looks like the exact same thing as: *frame_pointer = frame_pointer; ... obfuscated through a thick layer of type casts? Thanks, Ingo
On Mon, 5 Jul 2021 10:34:58 +0200 Ingo Molnar <mingo@kernel.org> wrote: > > * Masami Hiramatsu <mhiramat@kernel.org> wrote: > > > In x86, kretprobe trampoline address on the stack frame will > > be replaced with the real return address after returning from > > trampoline_handler. Before fixing the return address, the real > > return address can be found in the current->kretprobe_instances. > > > > However, since there is a window between updating the > > current->kretprobe_instances and fixing the address on the stack, > > if an interrupt caused at that timing and the interrupt handler > > does stacktrace, it may fail to unwind because it can not get > > the correct return address from current->kretprobe_instances. > > > > This will minimize that window by fixing the return address > > right before updating current->kretprobe_instances. > > Is there still a window? I.e. is it "minimized" (to how big of a window?), > or eliminated? Oh, this will eliminate the window, because the return address is fixed before updating the 'current->kretprobe_instance'. > > > +void arch_kretprobe_fixup_return(struct pt_regs *regs, > > + unsigned long correct_ret_addr) > > +{ > > + unsigned long *frame_pointer; > > + > > + frame_pointer = ((unsigned long *)®s->sp) + 1; > > + > > + /* Replace fake return address with real one. */ > > + *frame_pointer = correct_ret_addr; > > Firstly, why does ®s->sp have to be forced to 'unsigned long *'? > > pt_regs::sp is 'unsigned long' on both 32-bit and 64-bit kernels AFAICS. Ah, right. > > Secondly, the new code modified by your patch now looks like this: > > frame_pointer = ((unsigned long *)®s->sp) + 1; > > + kretprobe_trampoline_handler(regs, frame_pointer); > > where: > > +void arch_kretprobe_fixup_return(struct pt_regs *regs, > + unsigned long correct_ret_addr) > +{ > + unsigned long *frame_pointer; > + > + frame_pointer = ((unsigned long *)®s->sp) + 1; > + > + /* Replace fake return address with real one. */ > + *frame_pointer = correct_ret_addr; > +} > > So we first do: > > frame_pointer = ((unsigned long *)®s->sp) + 1; > > ... and pass that in to arch_kretprobe_fixup_return() as > 'correct_ret_addr', which does: No, 'correct_ret_addr' is found from 'current->kretprobe_instances' /* Find correct address and all nodes for this frame. */ correct_ret_addr = (void *)__kretprobe_find_ret_addr(current, &node); > > + frame_pointer = ((unsigned long *)®s->sp) + 1; > + *frame_pointer = correct_ret_addr; > > ... which looks like the exact same thing as: > > *frame_pointer = frame_pointer; > > ... obfuscated through a thick layer of type casts? Thus it will be the same thing as *frame_pointer = __kretprobe_find_ret_addr(current, &node); Actually, this is a bit confusing because same 'frame_pointer' is calcurated twice from 'regs->sp'. This is because the return address is stored at 'frame_pointer' or not depends on the architecture. Thank you, > > Thanks, > > Ingo
diff --git a/arch/x86/kernel/kprobes/core.c b/arch/x86/kernel/kprobes/core.c index 4d040aaf969b..53c1dcfcb145 100644 --- a/arch/x86/kernel/kprobes/core.c +++ b/arch/x86/kernel/kprobes/core.c @@ -1032,6 +1032,7 @@ STACK_FRAME_NON_STANDARD(kretprobe_trampoline); #undef UNWIND_HINT_FUNC #define UNWIND_HINT_FUNC #endif + /* * When a retprobed function returns, this code saves registers and * calls trampoline_handler() runs, which calls the kretprobe's handler. @@ -1073,6 +1074,17 @@ asm( ); NOKPROBE_SYMBOL(kretprobe_trampoline); +void arch_kretprobe_fixup_return(struct pt_regs *regs, + unsigned long correct_ret_addr) +{ + unsigned long *frame_pointer; + + frame_pointer = ((unsigned long *)®s->sp) + 1; + + /* Replace fake return address with real one. */ + *frame_pointer = correct_ret_addr; +} + /* * Called from kretprobe_trampoline */ @@ -1090,8 +1102,7 @@ __used __visible void trampoline_handler(struct pt_regs *regs) regs->sp += sizeof(long); frame_pointer = ((unsigned long *)®s->sp) + 1; - /* Replace fake return address with real one. */ - *frame_pointer = kretprobe_trampoline_handler(regs, frame_pointer); + kretprobe_trampoline_handler(regs, frame_pointer); /* * Move flags to sp so that kretprobe_trapmoline can return * right after popf. diff --git a/include/linux/kprobes.h b/include/linux/kprobes.h index 08d3415e4418..259bdc80e708 100644 --- a/include/linux/kprobes.h +++ b/include/linux/kprobes.h @@ -197,6 +197,9 @@ extern void arch_prepare_kretprobe(struct kretprobe_instance *ri, struct pt_regs *regs); extern int arch_trampoline_kprobe(struct kprobe *p); +void arch_kretprobe_fixup_return(struct pt_regs *regs, + unsigned long correct_ret_addr); + void kretprobe_trampoline(void); /* * Since some architecture uses structured function pointer, diff --git a/kernel/kprobes.c b/kernel/kprobes.c index ba729ed05cb3..72e8125fb0e9 100644 --- a/kernel/kprobes.c +++ b/kernel/kprobes.c @@ -1881,6 +1881,12 @@ unsigned long kretprobe_find_ret_addr(struct task_struct *tsk, void *fp, } NOKPROBE_SYMBOL(kretprobe_find_ret_addr); +void __weak arch_kretprobe_fixup_return(struct pt_regs *regs, + unsigned long correct_ret_addr) +{ + /* Do nothing by default. */ +} + unsigned long __kretprobe_trampoline_handler(struct pt_regs *regs, void *frame_pointer) { @@ -1922,6 +1928,8 @@ unsigned long __kretprobe_trampoline_handler(struct pt_regs *regs, first = first->next; } + arch_kretprobe_fixup_return(regs, (unsigned long)correct_ret_addr); + /* Unlink all nodes for this frame. */ first = current->kretprobe_instances.first; current->kretprobe_instances.first = node->next;