From 78bc978e9558d677b418656b1382fec706a0f822 Mon Sep 17 00:00:00 2001 From: Vyacheslav Cherkashin Date: Tue, 12 Jul 2016 14:49:36 +0300 Subject: [PATCH] ARM64: implement uretprobe for aarch32 mode Change-Id: I7719016204699bc0378175772d6750b9766bde96 Signed-off-by: Vyacheslav Cherkashin --- arch/arm/uprobe/swap_uprobe.c | 146 ++++++++++++++++++++++++++++ arch/arm/uprobe/swap_uprobe.h | 9 ++ uprobe/arch/arm64/swap-asm/swap_uprobes.c | 155 ++++++++++++++++++++++++++++-- uprobe/arch/arm64/swap-asm/swap_uprobes.h | 24 +---- uprobe/swap_uprobes.c | 12 ++- uprobe/swap_uprobes.h | 2 +- 6 files changed, 316 insertions(+), 32 deletions(-) diff --git a/arch/arm/uprobe/swap_uprobe.c b/arch/arm/uprobe/swap_uprobe.c index 3773e25..129771e 100644 --- a/arch/arm/uprobe/swap_uprobe.c +++ b/arch/arm/uprobe/swap_uprobe.c @@ -27,8 +27,12 @@ #include #include #include +#include "../probes/compat_arm64.h" +#define PTR_TO_U32(x) ((u32)(unsigned long)(x)) +#define U32_TO_PTR(x) ((void *)(unsigned long)(x)) + #define flush_insns(addr, size) \ flush_icache_range((unsigned long)(addr), \ (unsigned long)(addr) + (size)) @@ -120,3 +124,145 @@ void arch_disarm_uprobe_arm(struct uprobe *p, struct task_struct *task) flush_insns(vaddr, len); } } + +/** + * @brief Prepates uretprobe for ARM. + * + * @param ri Pointer to the uretprobe instance. + * @param regs Pointer to CPU register data. + * @return Error code. + */ +int prepare_uretprobe_arm(struct uretprobe_instance *ri, struct pt_regs *regs) +{ + unsigned long bp_offset; + + bp_offset = thumb_mode(regs) ? 0x1b : 4 * PROBES_TRAMP_RET_BREAK_IDX; + + /* save original return address */ + ri->ret_addr = (uprobe_opcode_t *)regs->ARM_lr; + + /* replace return address with break point adddress */ + regs->ARM_lr = (unsigned long)(ri->rp->up.insn) + bp_offset; + + /* save stack pointer address */ + ri->sp = (uprobe_opcode_t *)regs->ARM_sp; + + /* Set flag of current mode */ + ri->sp = (uprobe_opcode_t *)((long)ri->sp | !!thumb_mode(regs)); + + return 0; +} + +/** + * @brief Restores return address. + * + * @param orig_ret_addr Original return address. + * @param regs Pointer to CPU register data. + * @return Void. + */ +void set_orig_ret_addr_arm(unsigned long orig_ret_addr, struct pt_regs *regs) +{ + regs->ARM_lr = orig_ret_addr; + regs->ARM_pc = orig_ret_addr & ~0x1; + + if (regs->ARM_lr & 0x1) + regs->ARM_cpsr |= PSR_T_BIT; + else + regs->ARM_cpsr &= ~PSR_T_BIT; +} + +void arch_opcode_analysis_uretprobe_arm(struct uretprobe *rp) +{ + /* Remove retprobe if first insn overwrites lr */ + rp->thumb_noret = noret_thumb(rp->up.opcode); + rp->arm_noret = noret_arm(rp->up.opcode); +} + +unsigned long arch_get_trampoline_addr_arm(struct uprobe *p, + struct pt_regs *regs) +{ + return thumb_mode(regs) ? + PTR_TO_U32(p->insn) + 0x1b : + PTR_TO_U32(p->insn + + PROBES_TRAMP_RET_BREAK_IDX); +} + +unsigned long arch_tramp_by_ri_arm(struct uretprobe_instance *ri) +{ + /* Understand function mode */ + return (PTR_TO_U32(ri->sp) & 1) ? + PTR_TO_U32(ri->rp->up.insn) + 0x1b : + PTR_TO_U32(ri->rp->up.insn + + PROBES_TRAMP_RET_BREAK_IDX); +} + +int arch_disarm_urp_inst_arm(struct uretprobe_instance *ri, + struct task_struct *task) +{ + struct pt_regs *uregs = task_pt_regs(ri->task); + u32 ra = uregs->ARM_lr; + u32 vaddr, tramp, found = 0; + u32 sp = PTR_TO_U32(ri->sp) & ~1; + u32 ret_addr = PTR_TO_U32(ri->ret_addr); + u32 stack = sp - 4 * (RETPROBE_STACK_DEPTH + 1); + u32 buf[RETPROBE_STACK_DEPTH]; + int i, ret; + + vaddr = PTR_TO_U32(ri->rp->up.addr); + tramp = arch_tramp_by_ri_arm(ri); + + /* check stack */ + ret = read_proc_vm_atomic(task, stack, buf, sizeof(buf)); + if (ret != sizeof(buf)) { + pr_info("---> %s (%d/%d): failed to read stack from %08x\n", + task->comm, task->tgid, task->pid, stack); + ret = -EFAULT; + goto check_lr; + } + + /* search the stack from the bottom */ + for (i = RETPROBE_STACK_DEPTH - 1; i >= 0; i--) { + if (buf[i] == tramp) { + found = stack + 4 * i; + break; + } + } + + if (!found) { + ret = -ESRCH; + goto check_lr; + } + + pr_info("---> %s (%d/%d): trampoline found at " + "%08x (%08x /%+d) - %x, set ret_addr=%08x\n", + task->comm, task->tgid, task->pid, + found, sp, + found - sp, vaddr, ret_addr); + ret = write_proc_vm_atomic(task, found, &ret_addr, 4); + if (ret != 4) { + pr_info("---> %s (%d/%d): failed to write value to %08x", + task->comm, task->tgid, task->pid, found); + ret = -EFAULT; + } else { + ret = 0; + } + +check_lr: /* check lr anyway */ + if (ra == tramp) { + pr_info("---> %s (%d/%d): trampoline found at " + "lr = %08x - %x, set ret_addr=%08x\n", + task->comm, task->tgid, task->pid, ra, vaddr, + ret_addr); + + /* set ret_addr */ + uregs->ARM_lr = ret_addr; + ret = 0; + } else if (ret) { + pr_info("---> %s (%d/%d): trampoline NOT found at " + "sp=%08x, lr=%08x - %x, ret_addr=%08x\n", + task->comm, task->tgid, task->pid, + sp, ra, vaddr, ret_addr); + } + + return ret; +} diff --git a/arch/arm/uprobe/swap_uprobe.h b/arch/arm/uprobe/swap_uprobe.h index ba645b3..4ae91a8 100644 --- a/arch/arm/uprobe/swap_uprobe.h +++ b/arch/arm/uprobe/swap_uprobe.h @@ -87,6 +87,15 @@ int arch_prepare_uprobe_arm(struct uprobe *p); int arch_arm_uprobe_arm(struct uprobe *p); void arch_disarm_uprobe_arm(struct uprobe *p, struct task_struct *task); +int prepare_uretprobe_arm(struct uretprobe_instance *ri, struct pt_regs *regs); +void set_orig_ret_addr_arm(unsigned long orig_ret_addr, struct pt_regs *regs); +void arch_opcode_analysis_uretprobe_arm(struct uretprobe *rp); +unsigned long arch_get_trampoline_addr_arm(struct uprobe *p, + struct pt_regs *regs); +unsigned long arch_tramp_by_ri_arm(struct uretprobe_instance *ri); +int arch_disarm_urp_inst_arm(struct uretprobe_instance *ri, + struct task_struct *task); + #endif /* _SWAP_ASM_ARM_UPROBE_H */ diff --git a/uprobe/arch/arm64/swap-asm/swap_uprobes.c b/uprobe/arch/arm64/swap-asm/swap_uprobes.c index 1be33c0..0abe9d0 100644 --- a/uprobe/arch/arm64/swap-asm/swap_uprobes.c +++ b/uprobe/arch/arm64/swap-asm/swap_uprobes.c @@ -28,6 +28,7 @@ #include #include /* FIXME: remove it */ #include +#include #include #include "uprobes-arm64.h" @@ -93,24 +94,111 @@ static void reset_current_uprobe(void) } -int arch_prepare_uretprobe(struct uretprobe_instance *ri, - struct pt_regs *regs) +static int prepare_uretprobe_arm64(struct uretprobe_instance *ri, + struct pt_regs *regs) { unsigned long bp_addr = (unsigned long)(ri->rp->up.insn + URP_RET_BREAK_IDX); + unsigned long ret_addr = regs->regs[30] | ARM64_MODE_VADDR_MASK; ri->sp = (kprobe_opcode_t *)regs->sp; + ri->ret_addr = (kprobe_opcode_t *)ret_addr; /* replace the return address (regs[30] - lr) */ - ri->ret_addr = (kprobe_opcode_t *)regs->regs[30]; regs->regs[30] = bp_addr; return 0; } +int arch_prepare_uretprobe(struct uretprobe_instance *ri, + struct pt_regs *regs) +{ + if (get_arch_mode((unsigned long)ri->rp->up.addr) == AM_ARM64) + return prepare_uretprobe_arm64(ri, regs); + else + return prepare_uretprobe_arm(ri, regs); +} + +static void arch_opcode_analysis_uretprobe_arm64(struct uretprobe *rp) +{ + WARN(1, "not implemented"); /* FIXME: to implement */ +} + +void arch_opcode_analysis_uretprobe(struct uretprobe *rp) +{ + if (get_arch_mode((unsigned long)rp->up.addr) == AM_ARM64) + arch_opcode_analysis_uretprobe_arm64(rp); + else + arch_opcode_analysis_uretprobe_arm(rp); +} + +static unsigned long arch_get_trampoline_addr_arm64(struct uprobe *p, + struct pt_regs *regs) +{ + WARN(1, "not implemented"); /* FIXME: to implement */ + return 0; +} + +/** + * @brief Gets trampoline address. + * + * @param p Pointer to the kprobe. + * @param regs Pointer to CPU register data. + * @return Trampoline address. + */ +unsigned long arch_get_trampoline_addr(struct uprobe *p, struct pt_regs *regs) +{ + if (get_arch_mode((unsigned long)p->addr) == AM_ARM64) + return arch_get_trampoline_addr_arm64(p, regs); + else + return arch_get_trampoline_addr_arm(p, regs); +} + +static unsigned long arch_tramp_by_ri_arm64(struct uretprobe_instance *ri) +{ + WARN(1, "not implemented"); /* FIXME: to implement */ + return 0; +} + +unsigned long arch_tramp_by_ri(struct uretprobe_instance *ri) +{ + if (get_arch_mode((unsigned long)ri->rp->up.addr) == AM_ARM64) + return arch_tramp_by_ri_arm64(ri); + else + return arch_tramp_by_ri_arm(ri); +} + +static int arch_disarm_urp_inst_arm64(struct uretprobe_instance *ri, + struct task_struct *task) +{ + WARN(1, "not implemented"); /* FIXME: to implement */ + return 0; +} + +/** + * @brief Disarms uretprobe instance. + * + * @param ri Pointer to the uretprobe instance + * @param task Pointer to the task for which the uretprobe instance + * @return 0 on success,\n + * negative error code on error. + */ +int arch_disarm_urp_inst(struct uretprobe_instance *ri, + struct task_struct *task) +{ + if (get_arch_mode((unsigned long)ri->rp->up.addr) == AM_ARM64) + return arch_disarm_urp_inst_arm64(ri, task); + else + return arch_disarm_urp_inst_arm(ri, task); +} + void arch_set_orig_ret_addr(unsigned long orig_ret_addr, struct pt_regs *regs) { - regs->pc = orig_ret_addr; + if (get_arch_mode(orig_ret_addr) == AM_ARM64) { + regs->pc = get_real_addr(orig_ret_addr); + } else { + set_orig_ret_addr_arm(orig_ret_addr, regs); + } } static int make_urp_arm64(struct uprobe *p) @@ -134,8 +222,6 @@ static int make_urp_arm64(struct uprobe *p) pr_err("%s failed to write memory %p!\n", __func__, utramp); } - add_uprobe_table(p); - return 0; } @@ -285,6 +371,11 @@ int arch_prepare_uprobe(struct uprobe *p) ret = arch_prepare_uprobe_arm(p); } + if (!ret) { + /* for uretprobe */ + add_uprobe_table(p); + } + return ret; } @@ -467,8 +558,37 @@ static void arch_prepare_singlestep(struct uprobe *p, struct pt_regs *regs) } } +static int urp_handler_aarch32(struct pt_regs *regs, pid_t tgid) +{ + struct uprobe *p; + unsigned long vaddr = regs->pc; + unsigned long offset_bp = compat_thumb_mode(regs) ? + 0x1a : + 4 * PROBES_TRAMP_RET_BREAK_IDX; + unsigned long tramp_addr = vaddr - offset_bp; + unsigned long flags; + + local_irq_save(flags); + p = get_uprobe_by_insn_slot((void *)tramp_addr, tgid, regs); + if (unlikely(p == NULL)) { + local_irq_restore(flags); + + pr_info("no_uprobe: Not one of ours: let kernel handle it %lx\n", + vaddr); + return 1; + } + + get_up(p); + local_irq_restore(flags); + trampoline_uprobe_handler(p, regs); + put_up(p); + + return 0; +} + static int uprobe_handler_aarch32(struct pt_regs *regs, u32 instr) { + int ret = 0; struct uprobe *p; unsigned long flags; unsigned long vaddr = regs->pc | !!compat_thumb_mode(regs); @@ -488,9 +608,30 @@ static int uprobe_handler_aarch32(struct pt_regs *regs, u32 instr) put_up(p); } else { local_irq_restore(flags); + ret = urp_handler_aarch32(regs, tgid); + + /* check ARM/THUMB CPU mode matches installed probe mode */ + if (ret == 1) { + vaddr ^= 1; + + local_irq_save(flags); + p = get_uprobe((uprobe_opcode_t *)vaddr, tgid); + if (p) { + get_up(p); + local_irq_restore(flags); + pr_err("invalid mode: thumb=%d addr=%p insn=%08x\n", + !!compat_thumb_mode(regs), p->addr, p->opcode); + ret = 0; + + disarm_uprobe(p, current); + put_up(p); + } else { + local_irq_restore(flags); + } + } } - return 0; + return ret; } diff --git a/uprobe/arch/arm64/swap-asm/swap_uprobes.h b/uprobe/arch/arm64/swap-asm/swap_uprobes.h index 5494e08..ef87fa1 100644 --- a/uprobe/arch/arm64/swap-asm/swap_uprobes.h +++ b/uprobe/arch/arm64/swap-asm/swap_uprobes.h @@ -24,6 +24,7 @@ #include +#include #include @@ -125,29 +126,14 @@ int arch_arm_uprobe(struct uprobe *p); void arch_disarm_uprobe(struct uprobe *p, struct task_struct *task); -static inline unsigned long arch_get_trampoline_addr(struct uprobe *p, - struct pt_regs *regs) -{ - WARN(1, "not implemented"); /* FIXME: to implement */ - return 0; -} - +unsigned long arch_get_trampoline_addr(struct uprobe *p, struct pt_regs *regs); void arch_set_orig_ret_addr(unsigned long orig_ret_addr, struct pt_regs *regs); int arch_prepare_uretprobe(struct uretprobe_instance *ri, struct pt_regs *regs); -static inline int arch_opcode_analysis_uretprobe(struct uretprobe *rp) -{ - WARN(1, "not implemented"); /* FIXME: to implement */ - return 0; -} - -static inline int arch_disarm_urp_inst(struct uretprobe_instance *ri, - struct task_struct *task) -{ - WARN(1, "not implemented"); /* FIXME: to implement */ - return 0; -} +void arch_opcode_analysis_uretprobe(struct uretprobe *rp); +int arch_disarm_urp_inst(struct uretprobe_instance *ri, + struct task_struct *task); static inline void arch_ujprobe_return(void) { diff --git a/uprobe/swap_uprobes.c b/uprobe/swap_uprobes.c index 3ef0857..031dac9 100644 --- a/uprobe/swap_uprobes.c +++ b/uprobe/swap_uprobes.c @@ -752,16 +752,18 @@ int trampoline_uprobe_handler(struct uprobe *p, struct pt_regs *regs) static int pre_handler_uretprobe(struct uprobe *p, struct pt_regs *regs) { struct uretprobe *rp = container_of(p, struct uretprobe, up); -#ifdef CONFIG_ARM - int noret = thumb_mode(regs) ? rp->thumb_noret : rp->arm_noret; -#endif struct uretprobe_instance *ri; int ret = 0; -#ifdef CONFIG_ARM +#if defined(CONFIG_ARM) || defined(CONFIG_ARM64) +# if defined(CONFIG_ARM64) +# define thumb_mode(regs) compat_thumb_mode(regs) +# endif /* defined(CONFIG_ARM64) */ + int noret = thumb_mode(regs) ? rp->thumb_noret : rp->arm_noret; + if (noret) return 0; -#endif +#endif /* defined(CONFIG_ARM) || defined(CONFIG_ARM64) */ /* TODO: consider to only swap the * RA after the last pre_handler fired */ diff --git a/uprobe/swap_uprobes.h b/uprobe/swap_uprobes.h index 5554bcd..20d4027 100644 --- a/uprobe/swap_uprobes.h +++ b/uprobe/swap_uprobes.h @@ -150,7 +150,7 @@ struct uretprobe { struct hlist_head free_instances; /**< Free instances list */ struct hlist_head used_instances; /**< Used instances list */ -#ifdef CONFIG_ARM +#if defined(CONFIG_ARM) || defined(CONFIG_ARM64) unsigned arm_noret:1; /**< No-return flag for ARM */ unsigned thumb_noret:1; /**< No-return flag for Thumb */ #endif -- 2.7.4