[PATCH] arm64: kretprobes: acquire the regs via a BRK exception
Masami Hiramatsu (Google)
mhiramat at kernel.org
Sat Feb 17 04:35:04 PST 2024
Hi Mark,
On Thu, 8 Feb 2024 14:59:16 +0000
Mark Rutland <mark.rutland at arm.com> wrote:
> On arm64, kprobes always take an exception and so create a struct
> pt_regs through the usual exception entry logic. Similarly kretprobes
> taskes and exception for function entry, but for function returns it
> uses a trampoline which attempts to create a struct pt_regs without
> taking an exception.
>
> This is problematic for a few reasons, including:
>
> 1) The kretprobes trampoline neither saves nor restores all of the
> portions of PSTATE. Before invoking the handler it saves a number of
> portions of PSTATE, and after returning from the handler it restores
> NZCV before returning to the original return address provided by the
> handler.
>
> 2) The kretprobe trampoline constructs the PSTATE value piecemeal from
> special purpose registers as it cannot read all of PSTATE atomically
> without taking an exception. This is somewhat fragile, and it's not
> possible to reliably recover PSTATE information which only exists on
> some physical CPUs (e.g. when SSBS support is mismatched).
>
> Today the kretprobes trampoline does not record:
>
> - BTYPE
> - SSBS
> - ALLINT
> - SS
> - PAN
> - UAO
> - DIT
> - TCO
>
> ... and this will only get worse with future architecture extensions
> which add more PSTATE bits.
>
> 3) The kretprobes trampoline doesn't store portions of struct pt_regs
> (e.g. the PMR value when using pseudo-NMIs). Due to this, helpers
> which operate on a struct pt_regs, such as interrupts_enabled(), may
> not work correctly.
>
> 4) The function entry and function exit handlers run in different
> contexts. The entry handler will always be run in a debug exception
> context (which is currently treated as an NMI), but the return will
> be treated as whatever context the instrumented function was executed
> in. The differences between these contexts are liable to cause
> problems (e.g. as the two can be differently interruptible or
> preemptible, adversely affecting synchronization between the
> handlers).
>
> 5) As the kretprobes trampoline runs in the same context as the code
> being probed, it is subject to the same single-stepping context,
> which may not be desirable if this is being driven by the kprobes
> handlers.
>
> Overall, this is fragile, painful to maintain, and gets in the way of
> supporting other things (e.g. RELIABLE_STACKTRACE, FEAT_NMI).
I agree all of those reasons.
>
> This patch addresses these issues by replacing the kretprobes trampoline
> with a `BRK` instruction, and using an exception boundary to acquire and
> restore the regs, in the same way as the regular kprobes trampoline.
This design and code looks good to me.
>
> Ive tested this atop v6.8-rc3:
>
> | KTAP version 1
> | 1..1
> | KTAP version 1
> | # Subtest: kprobes_test
> | # module: test_kprobes
> | 1..7
> | ok 1 test_kprobe
> | ok 2 test_kprobes
> | ok 3 test_kprobe_missed
> | ok 4 test_kretprobe
> | ok 5 test_kretprobes
> | ok 6 test_stacktrace_on_kretprobe
> | ok 7 test_stacktrace_on_nested_kretprobe
> | # kprobes_test: pass:7 fail:0 skip:0 total:7
> | # Totals: pass:7 fail:0 skip:0 total:7
> | ok 1 kprobes_test
Acked-by: Masami Hiramatsu (Google) <mhiramat at kernel.org>
Thank you!
>
> Signed-off-by: Mark Rutland <mark.rutland at arm.com>
> Cc: Catalin Marinas <catalin.marinas at arm.com>
> Cc: Will Deacon <will at kernel.org>
> Cc: Florent Revest <revest at chromium.org>
> Cc: Masami Hiramatsu <mhiramat at kernel.org>
> Cc: Steven Rostedt <rostedt at goodmis.org>
> ---
> arch/arm64/include/asm/brk-imm.h | 2 +
> arch/arm64/kernel/probes/kprobes.c | 21 +++--
> arch/arm64/kernel/probes/kprobes_trampoline.S | 78 ++-----------------
> 3 files changed, 24 insertions(+), 77 deletions(-)
>
> diff --git a/arch/arm64/include/asm/brk-imm.h b/arch/arm64/include/asm/brk-imm.h
> index 1abdcd508a113..beb42c62b6acc 100644
> --- a/arch/arm64/include/asm/brk-imm.h
> +++ b/arch/arm64/include/asm/brk-imm.h
> @@ -11,6 +11,7 @@
> * 0x004: for installing kprobes
> * 0x005: for installing uprobes
> * 0x006: for kprobe software single-step
> + * 0x007: for kretprobe return
> * Allowed values for kgdb are 0x400 - 0x7ff
> * 0x100: for triggering a fault on purpose (reserved)
> * 0x400: for dynamic BRK instruction
> @@ -23,6 +24,7 @@
> #define KPROBES_BRK_IMM 0x004
> #define UPROBES_BRK_IMM 0x005
> #define KPROBES_BRK_SS_IMM 0x006
> +#define KRETPROBES_BRK_IMM 0x007
> #define FAULT_BRK_IMM 0x100
> #define KGDB_DYN_DBG_BRK_IMM 0x400
> #define KGDB_COMPILED_DBG_BRK_IMM 0x401
> diff --git a/arch/arm64/kernel/probes/kprobes.c b/arch/arm64/kernel/probes/kprobes.c
> index 70b91a8c6bb3f..327855a11df2f 100644
> --- a/arch/arm64/kernel/probes/kprobes.c
> +++ b/arch/arm64/kernel/probes/kprobes.c
> @@ -371,6 +371,21 @@ static struct break_hook kprobes_break_ss_hook = {
> .fn = kprobe_breakpoint_ss_handler,
> };
>
> +static int __kprobes
> +kretprobe_breakpoint_handler(struct pt_regs *regs, unsigned long esr)
> +{
> + if (regs->pc != (unsigned long)__kretprobe_trampoline)
> + return DBG_HOOK_ERROR;
> +
> + regs->pc = kretprobe_trampoline_handler(regs, (void *)regs->regs[29]);
> + return DBG_HOOK_HANDLED;
> +}
> +
> +static struct break_hook kretprobes_break_hook = {
> + .imm = KRETPROBES_BRK_IMM,
> + .fn = kretprobe_breakpoint_handler,
> +};
> +
> /*
> * Provide a blacklist of symbols identifying ranges which cannot be kprobed.
> * This blacklist is exposed to userspace via debugfs (kprobes/blacklist).
> @@ -396,11 +411,6 @@ int __init arch_populate_kprobe_blacklist(void)
> return ret;
> }
>
> -void __kprobes __used *trampoline_probe_handler(struct pt_regs *regs)
> -{
> - return (void *)kretprobe_trampoline_handler(regs, (void *)regs->regs[29]);
> -}
> -
> void __kprobes arch_prepare_kretprobe(struct kretprobe_instance *ri,
> struct pt_regs *regs)
> {
> @@ -420,6 +430,7 @@ int __init arch_init_kprobes(void)
> {
> register_kernel_break_hook(&kprobes_break_hook);
> register_kernel_break_hook(&kprobes_break_ss_hook);
> + register_kernel_break_hook(&kretprobes_break_hook);
>
> return 0;
> }
> diff --git a/arch/arm64/kernel/probes/kprobes_trampoline.S b/arch/arm64/kernel/probes/kprobes_trampoline.S
> index 9a6499bed58b0..a362f3dbb3d11 100644
> --- a/arch/arm64/kernel/probes/kprobes_trampoline.S
> +++ b/arch/arm64/kernel/probes/kprobes_trampoline.S
> @@ -4,83 +4,17 @@
> */
>
> #include <linux/linkage.h>
> -#include <asm/asm-offsets.h>
> +#include <asm/asm-bug.h>
> #include <asm/assembler.h>
>
> .text
>
> - .macro save_all_base_regs
> - stp x0, x1, [sp, #S_X0]
> - stp x2, x3, [sp, #S_X2]
> - stp x4, x5, [sp, #S_X4]
> - stp x6, x7, [sp, #S_X6]
> - stp x8, x9, [sp, #S_X8]
> - stp x10, x11, [sp, #S_X10]
> - stp x12, x13, [sp, #S_X12]
> - stp x14, x15, [sp, #S_X14]
> - stp x16, x17, [sp, #S_X16]
> - stp x18, x19, [sp, #S_X18]
> - stp x20, x21, [sp, #S_X20]
> - stp x22, x23, [sp, #S_X22]
> - stp x24, x25, [sp, #S_X24]
> - stp x26, x27, [sp, #S_X26]
> - stp x28, x29, [sp, #S_X28]
> - add x0, sp, #PT_REGS_SIZE
> - stp lr, x0, [sp, #S_LR]
> - /*
> - * Construct a useful saved PSTATE
> - */
> - mrs x0, nzcv
> - mrs x1, daif
> - orr x0, x0, x1
> - mrs x1, CurrentEL
> - orr x0, x0, x1
> - mrs x1, SPSel
> - orr x0, x0, x1
> - stp xzr, x0, [sp, #S_PC]
> - .endm
> -
> - .macro restore_all_base_regs
> - ldr x0, [sp, #S_PSTATE]
> - and x0, x0, #(PSR_N_BIT | PSR_Z_BIT | PSR_C_BIT | PSR_V_BIT)
> - msr nzcv, x0
> - ldp x0, x1, [sp, #S_X0]
> - ldp x2, x3, [sp, #S_X2]
> - ldp x4, x5, [sp, #S_X4]
> - ldp x6, x7, [sp, #S_X6]
> - ldp x8, x9, [sp, #S_X8]
> - ldp x10, x11, [sp, #S_X10]
> - ldp x12, x13, [sp, #S_X12]
> - ldp x14, x15, [sp, #S_X14]
> - ldp x16, x17, [sp, #S_X16]
> - ldp x18, x19, [sp, #S_X18]
> - ldp x20, x21, [sp, #S_X20]
> - ldp x22, x23, [sp, #S_X22]
> - ldp x24, x25, [sp, #S_X24]
> - ldp x26, x27, [sp, #S_X26]
> - ldp x28, x29, [sp, #S_X28]
> - .endm
> -
> SYM_CODE_START(__kretprobe_trampoline)
> - sub sp, sp, #PT_REGS_SIZE
> -
> - save_all_base_regs
> -
> - /* Setup a frame pointer. */
> - add x29, sp, #S_FP
> -
> - mov x0, sp
> - bl trampoline_probe_handler
> /*
> - * Replace trampoline address in lr with actual orig_ret_addr return
> - * address.
> + * Trigger a breakpoint exception. The PC will be adjusted by
> + * kretprobe_breakpoint_handler(), and no subsequent instructions will
> + * be executed from the trampoline.
> */
> - mov lr, x0
> -
> - /* The frame pointer (x29) is restored with other registers. */
> - restore_all_base_regs
> -
> - add sp, sp, #PT_REGS_SIZE
> - ret
> -
> + brk #KRETPROBES_BRK_IMM
> + ASM_BUG()
> SYM_CODE_END(__kretprobe_trampoline)
> --
> 2.30.2
>
--
Masami Hiramatsu (Google) <mhiramat at kernel.org>
More information about the linux-arm-kernel
mailing list