summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnton Arapov <anton@redhat.com>2012-11-01 00:00:00 +0200
committerAnton Arapov <anton@redhat.com>2012-11-01 13:42:33 +0100
commit64f2f461a27d90509299071c5d60cc3a0b7d6f38 (patch)
tree64af5eead0a7841c6742ee08dcbe263d31f4c5a8
parent25867805ad90019c10b8e71a78ea394bf344007c (diff)
downloadkernel-uprobes-64f2f461a27d90509299071c5d60cc3a0b7d6f38.zip
kernel-uprobes-64f2f461a27d90509299071c5d60cc3a0b7d6f38.tar.gz
kernel-uprobes-64f2f461a27d90509299071c5d60cc3a0b7d6f38.tar.xz
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.h10
-rw-r--r--arch/x86/kernel/uprobes.c58
2 files changed, 68 insertions, 0 deletions
diff --git a/arch/x86/include/asm/uprobes.h b/arch/x86/include/asm/uprobes.h
index 8ff8be7..8f905fb 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 aafa555..fb2c047 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);
+}