All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2] arm64: ftrace: use HAVE_FUNCTION_GRAPH_RET_ADDR_PTR
@ 2021-10-29 16:22 Mark Rutland
  2021-11-02 18:38 ` Mark Brown
  2021-11-03 13:19 ` Will Deacon
  0 siblings, 2 replies; 4+ messages in thread
From: Mark Rutland @ 2021-10-29 16:22 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: broonie, catalin.marinas, madvenka, mark.rutland, rostedt, will

When CONFIG_FUNCTION_GRAPH_TRACER is selected and the function graph
tracer is in use, unwind_frame() may erroneously associate a traced
function with an incorrect return address. This can happen when starting
an unwind from a pt_regs, or when unwinding across an exception
boundary.

This can be seen when recording with perf while the function graph
tracer is in use. For example:

| # echo function_graph > /sys/kernel/debug/tracing/current_tracer
| # perf record -g -e raw_syscalls:sys_enter:k /bin/true
| # perf report

... reports the callchain erroneously as:

| el0t_64_sync
| el0t_64_sync_handler
| el0_svc_common.constprop.0
| perf_callchain
| get_perf_callchain
| syscall_trace_enter
| syscall_trace_enter

... whereas when the function graph tracer is not in use, it reports:

| el0t_64_sync
| el0t_64_sync_handler
| el0_svc
| do_el0_svc
| el0_svc_common.constprop.0
| syscall_trace_enter
| syscall_trace_enter

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 arbitrary 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).

   This can result in erroneous entries being reported in a backtrace
   recorded by perf or kfence when the function graph tracer is in use.
   Currently show_regs() is unaffected as dump_backtrace() performs an
   initial unwind.

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.

   This can result in erroneous entries being reported in a backtrace
   performed by any in-kernel unwinder when that backtrace crosses an
   exception boundary, with entries after the boundary being reported
   incorrectly. This includes perf, kfence, show_regs(), panic(), etc.

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 return
address. We can use HAVE_FUNCTION_GRAPH_RET_ADDR_PTR to associate
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.

Since ftrace_graph_ret_addr() requires the return address with the PAC
stripped, the stripping of the PAC is moved before the fixup of the
rewritten address. As we would unconditionally strip the PAC, moving
this earlier is not harmful, and we can avoid a redundant strip in the
return address fixup code.

I've tested this with the perf case above, the ftrace selftests, and
a number of ad-hoc unwinder tests. The tests all pass, and I have seen
no unexpected behaviour as a result of this change. I've tested with
pointer authentication under QEMU TCG where magic-sysrq+l correctly
recovers the original return addresses.

Note that 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. Were we to consume the LR in all cases this would
result 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 LR
| 	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().

Link: https://lore.kernel.org/r/20211025164925.GB2001@C02TD0UTHF1T.local
Link: https://lore.kernel.org/r/20211027132529.30027-1-mark.rutland@arm.com
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Madhavan T. Venkataraman <madvenka@linux.microsoft.com>
Cc: Mark Brown <broonie@kernel.org>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Will Deacon <will@kernel.org>
---
 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      | 18 ++++++++----------
 4 files changed, 22 insertions(+), 19 deletions(-)

Since v1 [1]:
* Fix typos
* Provide example in the commit message
* More clearly state the user visible impact
* Add Link tags for original inpline posting and v1
* Fix PAC stripping
* Describe testing this has seen

[1] https://lore.kernel.org/r/20211027132529.30027-1-mark.rutland@arm.com

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..13ea4e4a4d27 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.
@@ -113,25 +110,26 @@ int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame)
 	frame->prev_fp = fp;
 	frame->prev_type = info.type;
 
+	frame->pc = ptrauth_strip_insn_pac(frame->pc);
+
 #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;
+		(frame->pc == (unsigned long)return_to_handler)) {
+		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);
+		if (WARN_ON_ONCE(frame->pc == orig_pc))
 			return -EINVAL;
-		frame->pc = ret_stack->ret;
+		frame->pc = orig_pc;
 	}
 #endif /* CONFIG_FUNCTION_GRAPH_TRACER */
 
-	frame->pc = ptrauth_strip_insn_pac(frame->pc);
-
 	return 0;
 }
 NOKPROBE_SYMBOL(unwind_frame);
-- 
2.11.0


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH v2] arm64: ftrace: use HAVE_FUNCTION_GRAPH_RET_ADDR_PTR
  2021-10-29 16:22 [PATCH v2] arm64: ftrace: use HAVE_FUNCTION_GRAPH_RET_ADDR_PTR Mark Rutland
@ 2021-11-02 18:38 ` Mark Brown
  2021-11-03 13:19 ` Will Deacon
  1 sibling, 0 replies; 4+ messages in thread
From: Mark Brown @ 2021-11-02 18:38 UTC (permalink / raw)
  To: Mark Rutland; +Cc: linux-arm-kernel, catalin.marinas, madvenka, rostedt, will


[-- Attachment #1.1: Type: text/plain, Size: 819 bytes --]

On Fri, Oct 29, 2021 at 05:22:45PM +0100, Mark Rutland wrote:

> 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 return
> address. We can use HAVE_FUNCTION_GRAPH_RET_ADDR_PTR to associate
> 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.

As a bonus since we're relying on core code more the result is also
simpler.

Reviewed-by: Mark Brown <broonie@kernel.org>

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v2] arm64: ftrace: use HAVE_FUNCTION_GRAPH_RET_ADDR_PTR
  2021-10-29 16:22 [PATCH v2] arm64: ftrace: use HAVE_FUNCTION_GRAPH_RET_ADDR_PTR Mark Rutland
  2021-11-02 18:38 ` Mark Brown
@ 2021-11-03 13:19 ` Will Deacon
  2021-11-15 14:46   ` Mark Rutland
  1 sibling, 1 reply; 4+ messages in thread
From: Will Deacon @ 2021-11-03 13:19 UTC (permalink / raw)
  To: linux-arm-kernel, Mark Rutland
  Cc: catalin.marinas, kernel-team, Will Deacon, broonie, madvenka, rostedt

On Fri, 29 Oct 2021 17:22:45 +0100, Mark Rutland wrote:
> When CONFIG_FUNCTION_GRAPH_TRACER is selected and the function graph
> tracer is in use, unwind_frame() may erroneously associate a traced
> function with an incorrect return address. This can happen when starting
> an unwind from a pt_regs, or when unwinding across an exception
> boundary.
> 
> This can be seen when recording with perf while the function graph
> tracer is in use. For example:
> 
> [...]

Applied to arm64 (for-next/core), thanks!

[1/1] arm64: ftrace: use HAVE_FUNCTION_GRAPH_RET_ADDR_PTR
      https://git.kernel.org/arm64/c/552e196d88e5

Cheers,
-- 
Will

https://fixes.arm64.dev
https://next.arm64.dev
https://will.arm64.dev

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v2] arm64: ftrace: use HAVE_FUNCTION_GRAPH_RET_ADDR_PTR
  2021-11-03 13:19 ` Will Deacon
@ 2021-11-15 14:46   ` Mark Rutland
  0 siblings, 0 replies; 4+ messages in thread
From: Mark Rutland @ 2021-11-15 14:46 UTC (permalink / raw)
  To: Will Deacon
  Cc: linux-arm-kernel, catalin.marinas, kernel-team, broonie,
	madvenka, rostedt

Hi Will,

On Wed, Nov 03, 2021 at 01:19:45PM +0000, Will Deacon wrote:
> On Fri, 29 Oct 2021 17:22:45 +0100, Mark Rutland wrote:
> > When CONFIG_FUNCTION_GRAPH_TRACER is selected and the function graph
> > tracer is in use, unwind_frame() may erroneously associate a traced
> > function with an incorrect return address. This can happen when starting
> > an unwind from a pt_regs, or when unwinding across an exception
> > boundary.
> > 
> > This can be seen when recording with perf while the function graph
> > tracer is in use. For example:
> > 
> > [...]
> 
> Applied to arm64 (for-next/core), thanks!
> 
> [1/1] arm64: ftrace: use HAVE_FUNCTION_GRAPH_RET_ADDR_PTR
>       https://git.kernel.org/arm64/c/552e196d88e5

After this got applied, it got dropped due to a couple of (relatively
trivial) conflicts with:

  cd9bc2c9258816dc ("arm64: Recover kretprobe modified return address in stacktrace")
  
... since that was going via the ftrace tree.

Now that's in v5.16-rc1, are you happy to pick this as a fix for
v5.16-rc2?

I've push a version rebased atop v5.16-rc1 to:

  https://git.kernel.org/pub/scm/linux/kernel/git/mark/linux.git/log/?h=arm64/stacktrace/arch-stack-walk
  git://git.kernel.org/pub/scm/linux/kernel/git/mark/linux.git arm64/stacktrace/arch-stack-walk

... which I've re-tested as per the existig description in the commit
message.

If you'd like, I can send that out as a v3 so you have something to
refer to in a link tag. Otherwise, if you'd prefer to fix that up
yourself the resolution is pretty straightforward: delete the lines
using stackframe::graph.

Thanks,
Mark.

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2021-11-15 14:47 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-10-29 16:22 [PATCH v2] arm64: ftrace: use HAVE_FUNCTION_GRAPH_RET_ADDR_PTR Mark Rutland
2021-11-02 18:38 ` Mark Brown
2021-11-03 13:19 ` Will Deacon
2021-11-15 14:46   ` Mark Rutland

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.