Skip to content

Commit

Permalink
rethook: x86: Add rethook x86 implementation
Browse files Browse the repository at this point in the history
Add rethook for x86 implementation. Most of the code
has been copied from kretprobes on x86.

Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
  • Loading branch information
mhiramat authored and intel-lab-lkp committed Jan 24, 2022
1 parent 5f4d918 commit 6366c7f
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 0 deletions.
1 change: 1 addition & 0 deletions arch/x86/Kconfig
Expand Up @@ -219,6 +219,7 @@ config X86
select HAVE_KPROBES_ON_FTRACE
select HAVE_FUNCTION_ERROR_INJECTION
select HAVE_KRETPROBES
select HAVE_RETHOOK
select HAVE_KVM
select HAVE_LIVEPATCH if X86_64
select HAVE_MIXED_BREAKPOINTS_REGS
Expand Down
4 changes: 4 additions & 0 deletions arch/x86/include/asm/unwind.h
Expand Up @@ -5,6 +5,7 @@
#include <linux/sched.h>
#include <linux/ftrace.h>
#include <linux/kprobes.h>
#include <linux/rethook.h>
#include <asm/ptrace.h>
#include <asm/stacktrace.h>

Expand Down Expand Up @@ -107,6 +108,9 @@ static inline
unsigned long unwind_recover_kretprobe(struct unwind_state *state,
unsigned long addr, unsigned long *addr_p)
{
if (IS_ENABLED(CONFIG_RETHOOK) && is_rethook_trampoline(addr))
return rethook_find_ret_addr(state->task, (unsigned long)addr_p,
&state->kr_cur);
#ifdef CONFIG_KRETPROBES
return is_kretprobe_trampoline(addr) ?
kretprobe_find_ret_addr(state->task, addr_p, &state->kr_cur) :
Expand Down
1 change: 1 addition & 0 deletions arch/x86/kernel/Makefile
Expand Up @@ -106,6 +106,7 @@ obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += ftrace.o
obj-$(CONFIG_FTRACE_SYSCALLS) += ftrace.o
obj-$(CONFIG_X86_TSC) += trace_clock.o
obj-$(CONFIG_TRACING) += trace.o
obj-$(CONFIG_RETHOOK) += rethook.o
obj-$(CONFIG_CRASH_CORE) += crash_core_$(BITS).o
obj-$(CONFIG_KEXEC_CORE) += machine_kexec_$(BITS).o
obj-$(CONFIG_KEXEC_CORE) += relocate_kernel_$(BITS).o crash.o
Expand Down
115 changes: 115 additions & 0 deletions arch/x86/kernel/rethook.c
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* x86 implementation of rethook. Mostly copied from arch/x86/kernel/kprobes/core.c.
*/
#include <linux/bug.h>
#include <linux/rethook.h>
#include <linux/kprobes.h>

#include "kprobes/common.h"

/*
* Called from arch_rethook_trampoline
*/
__used __visible void arch_rethook_trampoline_callback(struct pt_regs *regs)
{
unsigned long *frame_pointer;

/* fixup registers */
regs->cs = __KERNEL_CS;
#ifdef CONFIG_X86_32
regs->gs = 0;
#endif
regs->ip = (unsigned long)&arch_rethook_trampoline;
regs->orig_ax = ~0UL;
regs->sp += sizeof(long);
frame_pointer = &regs->sp + 1;

/*
* The return address at 'frame_pointer' is recovered by the
* arch_rethook_fixup_return() which called from this
* rethook_trampoline_handler().
*/
rethook_trampoline_handler(regs, (unsigned long)frame_pointer);

/*
* Copy FLAGS to 'pt_regs::sp' so that arch_rethook_trapmoline()
* can do RET right after POPF.
*/
regs->sp = regs->flags;
}
NOKPROBE_SYMBOL(arch_rethook_trampoline_callback);

/*
* When a target function returns, this code saves registers and calls
* arch_rethook_trampoline_callback(), which calls the rethook handler.
*/
asm(
".text\n"
".global arch_rethook_trampoline\n"
".type arch_rethook_trampoline, @function\n"
"arch_rethook_trampoline:\n"
#ifdef CONFIG_X86_64
/* Push a fake return address to tell the unwinder it's a kretprobe. */
" pushq $arch_rethook_trampoline\n"
UNWIND_HINT_FUNC
/* Save the 'sp - 8', this will be fixed later. */
" pushq %rsp\n"
" pushfq\n"
SAVE_REGS_STRING
" movq %rsp, %rdi\n"
" call arch_rethook_trampoline_callback\n"
RESTORE_REGS_STRING
/* In the callback function, 'regs->flags' is copied to 'regs->sp'. */
" addq $8, %rsp\n"
" popfq\n"
#else
/* Push a fake return address to tell the unwinder it's a kretprobe. */
" pushl $arch_rethook_trampoline\n"
UNWIND_HINT_FUNC
/* Save the 'sp - 4', this will be fixed later. */
" pushl %esp\n"
" pushfl\n"
SAVE_REGS_STRING
" movl %esp, %eax\n"
" call arch_rethook_trampoline_callback\n"
RESTORE_REGS_STRING
/* In the callback function, 'regs->flags' is copied to 'regs->sp'. */
" addl $4, %esp\n"
" popfl\n"
#endif
" ret\n"
".size arch_rethook_trampoline, .-arch_rethook_trampoline\n"
);
NOKPROBE_SYMBOL(arch_rethook_trampoline);
/*
* arch_rethook_trampoline() skips updating frame pointer. The frame pointer
* saved in arch_rethook_trampoline_callback() points to the real caller
* function's frame pointer. Thus the arch_rethook_trampoline() doesn't have
* a standard stack frame with CONFIG_FRAME_POINTER=y.
* Let's mark it non-standard function. Anyway, FP unwinder can correctly
* unwind without the hint.
*/
STACK_FRAME_NON_STANDARD_FP(arch_rethook_trampoline);

/* This is called from rethook_trampoline_handler(). */
void arch_rethook_fixup_return(struct pt_regs *regs,
unsigned long correct_ret_addr)
{
unsigned long *frame_pointer = &regs->sp + 1;

/* Replace fake return address with real one. */
*frame_pointer = correct_ret_addr;
}

void arch_rethook_prepare(struct rethook_node *rh, struct pt_regs *regs)
{
unsigned long *stack = (unsigned long *)regs->sp;

rh->ret_addr = stack[0];
rh->frame = regs->sp;

/* Replace the return addr with trampoline addr */
stack[0] = (unsigned long) arch_rethook_trampoline;
}
NOKPROBE_SYMBOL(arch_rethook_prepare);

0 comments on commit 6366c7f

Please sign in to comment.