diff options
author | Anton Arapov <anton@redhat.com> | 2012-11-01 00:00:00 +0200 |
---|---|---|
committer | Anton Arapov <anton@redhat.com> | 2012-11-01 13:42:33 +0100 |
commit | 64f2f461a27d90509299071c5d60cc3a0b7d6f38 (patch) | |
tree | 64af5eead0a7841c6742ee08dcbe263d31f4c5a8 | |
parent | 25867805ad90019c10b8e71a78ea394bf344007c (diff) | |
download | kernel-uprobes-64f2f461a27d90509299071c5d60cc3a0b7d6f38.tar.gz kernel-uprobes-64f2f461a27d90509299071c5d60cc3a0b7d6f38.tar.xz kernel-uprobes-64f2f461a27d90509299071c5d60cc3a0b7d6f38.zip |
uretprobes: x86 arch specific code to hijack return address
These function hijack the return address, replaces it with a
"trampoline" a piece of code that contains a breakpoint instruction.
Signed-off-by: Anithra P Janakiraman <anithra@linux.vnet.ibm.com>
Signed-off-by: Anton Arapov <anton@redhat.com>
-rw-r--r-- | arch/x86/include/asm/uprobes.h | 10 | ||||
-rw-r--r-- | arch/x86/kernel/uprobes.c | 58 |
2 files changed, 68 insertions, 0 deletions
diff --git a/arch/x86/include/asm/uprobes.h b/arch/x86/include/asm/uprobes.h index 8ff8be7835a..8f905fbe9b3 100644 --- a/arch/x86/include/asm/uprobes.h +++ b/arch/x86/include/asm/uprobes.h @@ -47,6 +47,12 @@ struct arch_uprobe_task { #endif unsigned int saved_trap_nr; unsigned int saved_tf; + + /* + * Unexpected error in probepoint handling has left task's + * text or stack corrupted. Kill task ASAP. + */ + int doomed; }; extern int arch_uprobe_analyze_insn(struct arch_uprobe *aup, struct mm_struct *mm, unsigned long addr); @@ -55,4 +61,8 @@ extern int arch_uprobe_post_xol(struct arch_uprobe *aup, struct pt_regs *regs); extern bool arch_uprobe_xol_was_trapped(struct task_struct *tsk); extern int arch_uprobe_exception_notify(struct notifier_block *self, unsigned long val, void *data); extern void arch_uprobe_abort_xol(struct arch_uprobe *aup, struct pt_regs *regs); + +extern unsigned long arch_uretprobe_hijack_uret_addr(unsigned long trampoline_addr, + struct pt_regs *regs, struct arch_uprobe_task *autask); +extern unsigned long arch_uretprobe_predict_sp_at_ret(struct pt_regs *regs, struct task_struct *tsk); #endif /* _ASM_UPROBES_H */ diff --git a/arch/x86/kernel/uprobes.c b/arch/x86/kernel/uprobes.c index aafa5557b39..fb2c0473679 100644 --- a/arch/x86/kernel/uprobes.c +++ b/arch/x86/kernel/uprobes.c @@ -711,3 +711,61 @@ void arch_uprobe_disable_step(struct arch_uprobe *auprobe) regs->flags &= ~X86_EFLAGS_TF; } } + +unsigned long arch_uretprobe_hijack_uret_addr(unsigned long trampoline_addr, + struct pt_regs *regs, struct arch_uprobe_task *autask) +{ + int nleft; + unsigned long orig_ret_addr = 0; /* clear high bits for 32-bit apps */ + size_t rasize; + + rasize = is_ia32_task() ? 4 : 8; + nleft = copy_from_user(&orig_ret_addr, (const void __user *) regs->sp, + rasize); + if (unlikely(nleft != 0)) + return 0; + + if (orig_ret_addr == trampoline_addr) + /* + * There's another uretprobe on this function, and it was + * processed first, so the return address has already + * been hijacked. + */ + return orig_ret_addr; + + nleft = copy_to_user((void __user *) regs->sp, &trampoline_addr, + rasize); + if (unlikely(nleft != 0)) { + if (nleft != rasize) { + printk(KERN_ERR "uretprobe_entry_handler: " + "return address partially clobbered -- " + "pid=%d, %%sp=%#lx, %%ip=%#lx\n", + current->pid, regs->sp, regs->ip); + autask->doomed = 1; + } /* else nothing written, so no harm */ + + return 0; + } + + return orig_ret_addr; +} + +/* + * On x86_32, if a function returns a struct or union, the return + * value is copied into an area created by the caller. The address + * of this area is passed on the stack as a "hidden" first argument. + * When such a function returns, it uses a "ret $4" instruction to pop + * not only the return address but also the hidden arg. To accommodate + * such functions, we add 4 bytes of slop when predicting the return + * address. See PR #10078. + */ +#define STRUCT_RETURN_SLOP 4 + +unsigned long arch_uretprobe_predict_sp_at_ret(struct pt_regs *regs, + struct task_struct *tsk) +{ + if (test_tsk_thread_flag(tsk, TIF_IA32)) + return (unsigned long) (regs->sp + 4 + STRUCT_RETURN_SLOP); + else + return (unsigned long) (regs->sp + 8); +} |