[PATCH] arm64: ftrace: use HAVE_FUNCTION_GRAPH_RET_ADDR_PTR
Will Deacon
will at kernel.org
Thu Oct 28 01:52:25 PDT 2021
On Wed, Oct 27, 2021 at 02:25:28PM +0100, Mark Rutland wrote:
> When CONFIG_FUNCTION_GRAPH_TRACER is selected, and the function graph:
Weird ':' on the end of "graph"
> tracer is in use, unwind_frame() may erroneously asscociate a traced
associate
> function with an incorrect return address. This can happen when starting
> an unwind from a pt_regs, or when unwinding across an exception
> boundary.
>
> The underlying problem is that ftrace_graph_get_ret_stack() takes an
> index offset from the most recent entry added to the fgraph return
> stack. We start an unwind at offset 0, and increment the offset each
> time we encounter a rewritten return address (i.e. when we see
> `return_to_handler`). This is broken in two cases:
>
> 1) Between creating a pt_regs and starting the unwind, function calls
> may place entries on the stack, leaving an abitrary offset which we
> can only determine by performing a full unwind from the caller of the
> unwind code (and relying on none of the unwind code being
> instrumented). While this initial unwind is open-coded in
> dump_backtrace(), this is not performed for other unwinders such as
> perf_callchain_kernel().
What is the user-visible impact of this?
> 2) When unwinding across an exception boundary (whether continuing an
> unwind or starting a new unwind from regs), we currently always skip
> the LR of the interrupted context. Where this was live and contained
> a rewritten address, we won't consume the corresponding fgraph ret
> stack entry, leaving subsequent entries off-by-one.
>
> To fix this, we need to be able to uniquely identify each rewritten
> return address such that we can map this back to the original reeturn
return
> address. We can do so using HAVE_FUNCTION_GRAPH_RET_ADDR_PTR, and
> associating each rewritten return address with a unique location on the
> stack. As the return address is passed in the LR (and so is not
> guaranteed a unique location in memory), we use the FP upon entry to the
> function (i.e. the address of the caller's frame record) as the return
> address pointer. Any nested call will have a different FP value as the
> caller must create its own frame record and update FP to point to this.
>
> This doesn't fix the issue of skipping a live LR at an exception
> boundary, which is a more general problem and requires more substantial
> rework. Note that were we to consume the LR in all cases this would in
would in?
> warnings where the interrupted context's LR contains
> `return_to_handler`, but the FP has been altered, e.g.
>
> | func:
> | <--- ftrace entry ---> // logs FP & LR, rewrites FP
> | STP FP, LR, [SP, #-16]!
> | MOV FP, SP
> | <--- INTERRUPT --->
>
> ... as ftrace_graph_get_ret_stack() fill not find a matching entry,
> triggering the WARN_ON_ONCE() in unwind_frame().
Did you run the ftrace selftests with this? If not, please can you do it?
> Signed-off-by: Mark Rutland <mark.rutland at arm.com>
> Cc: Catalin Marinas <catalin.marinas at arm.com>
> Cc: Madhavan T. Venkataraman <madvenka at linux.microsoft.com>
> Cc: Mark Brown <broonie at kernel.org>
> Cc: Will Deacon <will at kernel.org>
(Adding rostedt FYI)
> ---
> arch/arm64/include/asm/ftrace.h | 11 +++++++++++
> arch/arm64/include/asm/stacktrace.h | 6 ------
> arch/arm64/kernel/ftrace.c | 6 +++---
> arch/arm64/kernel/stacktrace.c | 12 +++++-------
> 4 files changed, 19 insertions(+), 16 deletions(-)
>
> I spotted this latent issue when reviewing Madhavan's stack tracing rework:
>
> https://lore.kernel.org/r/20211015025847.17694-1-madvenka@linux.microsoft.com
>
> This supercedes the initial version I posted as an inline reply in:
>
> https://lore.kernel.org/r/20211025164925.GB2001@C02TD0UTHF1T.local
Please add a Link: tag referring to this.
> I believe we will need this as a prerequisite for other cleanups in that
> series. As it fixes an existing bug I'm posting it on its own.
>
> Mark.
>
> diff --git a/arch/arm64/include/asm/ftrace.h b/arch/arm64/include/asm/ftrace.h
> index 91fa4baa1a93..c96d47cb8f46 100644
> --- a/arch/arm64/include/asm/ftrace.h
> +++ b/arch/arm64/include/asm/ftrace.h
> @@ -12,6 +12,17 @@
>
> #define HAVE_FUNCTION_GRAPH_FP_TEST
>
> +/*
> + * HAVE_FUNCTION_GRAPH_RET_ADDR_PTR means that the architecture can provide a
> + * "return address pointer" which can be used to uniquely identify a return
> + * address which has been overwritten.
> + *
> + * On arm64 we use the address of the caller's frame record, which remains the
> + * same for the lifetime of the instrumented function, unlike the return
> + * address in the LR.
> + */
> +#define HAVE_FUNCTION_GRAPH_RET_ADDR_PTR
> +
> #ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS
> #define ARCH_SUPPORTS_FTRACE_OPS 1
> #else
> diff --git a/arch/arm64/include/asm/stacktrace.h b/arch/arm64/include/asm/stacktrace.h
> index 8aebc00c1718..9a319eca5cab 100644
> --- a/arch/arm64/include/asm/stacktrace.h
> +++ b/arch/arm64/include/asm/stacktrace.h
> @@ -46,9 +46,6 @@ struct stack_info {
> * @prev_type: The type of stack this frame record was on, or a synthetic
> * value of STACK_TYPE_UNKNOWN. This is used to detect a
> * transition from one stack to another.
> - *
> - * @graph: When FUNCTION_GRAPH_TRACER is selected, holds the index of a
> - * replacement lr value in the ftrace graph stack.
> */
> struct stackframe {
> unsigned long fp;
> @@ -56,9 +53,6 @@ struct stackframe {
> DECLARE_BITMAP(stacks_done, __NR_STACK_TYPES);
> unsigned long prev_fp;
> enum stack_type prev_type;
> -#ifdef CONFIG_FUNCTION_GRAPH_TRACER
> - int graph;
> -#endif
> };
>
> extern int unwind_frame(struct task_struct *tsk, struct stackframe *frame);
> diff --git a/arch/arm64/kernel/ftrace.c b/arch/arm64/kernel/ftrace.c
> index 7f467bd9db7a..3e5d0ed63eb7 100644
> --- a/arch/arm64/kernel/ftrace.c
> +++ b/arch/arm64/kernel/ftrace.c
> @@ -249,8 +249,6 @@ int __init ftrace_dyn_arch_init(void)
> * on the way back to parent. For this purpose, this function is called
> * in _mcount() or ftrace_caller() to replace return address (*parent) on
> * the call stack to return_to_handler.
> - *
> - * Note that @frame_pointer is used only for sanity check later.
> */
> void prepare_ftrace_return(unsigned long self_addr, unsigned long *parent,
> unsigned long frame_pointer)
> @@ -268,8 +266,10 @@ void prepare_ftrace_return(unsigned long self_addr, unsigned long *parent,
> */
> old = *parent;
>
> - if (!function_graph_enter(old, self_addr, frame_pointer, NULL))
> + if (!function_graph_enter(old, self_addr, frame_pointer,
> + (void *)frame_pointer)) {
> *parent = return_hooker;
> + }
> }
>
> #ifdef CONFIG_DYNAMIC_FTRACE
> diff --git a/arch/arm64/kernel/stacktrace.c b/arch/arm64/kernel/stacktrace.c
> index 8982a2b78acf..749b680b4999 100644
> --- a/arch/arm64/kernel/stacktrace.c
> +++ b/arch/arm64/kernel/stacktrace.c
> @@ -38,9 +38,6 @@ void start_backtrace(struct stackframe *frame, unsigned long fp,
> {
> frame->fp = fp;
> frame->pc = pc;
> -#ifdef CONFIG_FUNCTION_GRAPH_TRACER
> - frame->graph = 0;
> -#endif
>
> /*
> * Prime the first unwind.
> @@ -116,17 +113,18 @@ int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame)
> #ifdef CONFIG_FUNCTION_GRAPH_TRACER
> if (tsk->ret_stack &&
> (ptrauth_strip_insn_pac(frame->pc) == (unsigned long)return_to_handler)) {
> - struct ftrace_ret_stack *ret_stack;
> + unsigned long orig_pc;
> /*
> * This is a case where function graph tracer has
> * modified a return address (LR) in a stack frame
> * to hook a function return.
> * So replace it to an original value.
> */
> - ret_stack = ftrace_graph_get_ret_stack(tsk, frame->graph++);
> - if (WARN_ON_ONCE(!ret_stack))
> + orig_pc = ftrace_graph_ret_addr(tsk, NULL, frame->pc,
> + (void *)frame->fp);
Should we be using ptrauth_strip_insn_pac(frame->pc) again here?
ftrace_graph_ret_addr() appears to repeat the comparison against
return_to_handler.
> + if (WARN_ON_ONCE(frame->pc == orig_pc))
> return -EINVAL;
> - frame->pc = ret_stack->ret;
> + frame->pc = orig_pc;
> }
> #endif /* CONFIG_FUNCTION_GRAPH_TRACER */
This will conflict (trivially) with the kretprobe stuff in -next but might
be worth routing via the tracing tree anyway.
Will
More information about the linux-arm-kernel
mailing list