ARM: 9233/1: stacktrace: Skip frame pointer boundary check for call_with_stack()
authorLi Huafei <lihuafei1@huawei.com>
Fri, 26 Aug 2022 08:06:22 +0000 (09:06 +0100)
committerRussell King (Oracle) <rmk+kernel@armlinux.org.uk>
Tue, 4 Oct 2022 10:09:47 +0000 (11:09 +0100)
When using the frame pointer unwinder, it was found that the stack trace
output of stack_trace_save() is incomplete if the stack contains
call_with_stack():

 [0x7f00002c] dump_stack_task+0x2c/0x90 [hrtimer]
 [0x7f0000a0] hrtimer_hander+0x10/0x18 [hrtimer]
 [0x801a67f0] __hrtimer_run_queues+0x1b0/0x3b4
 [0x801a7350] hrtimer_run_queues+0xc4/0xd8
 [0x801a597c] update_process_times+0x3c/0x88
 [0x801b5a98] tick_periodic+0x50/0xd8
 [0x801b5bf4] tick_handle_periodic+0x24/0x84
 [0x8010ffc4] twd_handler+0x38/0x48
 [0x8017d220] handle_percpu_devid_irq+0xa8/0x244
 [0x80176e9c] generic_handle_domain_irq+0x2c/0x3c
 [0x8052e3a8] gic_handle_irq+0x7c/0x90
 [0x808ab15c] generic_handle_arch_irq+0x60/0x80
 [0x8051191c] call_with_stack+0x1c/0x20

For the frame pointer unwinder, unwind_frame() checks stackframe::fp by
stackframe::sp. Since call_with_stack() switches the SP from one stack
to another, stackframe::fp and stackframe: :sp will point to different
stacks, so we can no longer check stackframe::fp by stackframe::sp. Skip
checking stackframe::fp at this point to avoid this problem.

Signed-off-by: Li Huafei <lihuafei1@huawei.com>
Reviewed-by: Linus Waleij <linus.walleij@linaro.org>
Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
arch/arm/kernel/stacktrace.c
arch/arm/lib/call_with_stack.S

index d0fa203..af87040 100644 (file)
@@ -9,6 +9,8 @@
 #include <asm/stacktrace.h>
 #include <asm/traps.h>
 
+#include "reboot.h"
+
 #if defined(CONFIG_FRAME_POINTER) && !defined(CONFIG_ARM_UNWIND)
 /*
  * Unwind the current stack frame and store the new register values in the
  * Note that with framepointer enabled, even the leaf functions have the same
  * prologue and epilogue, therefore we can ignore the LR value in this case.
  */
-int notrace unwind_frame(struct stackframe *frame)
+
+extern unsigned long call_with_stack_end;
+
+static int frame_pointer_check(struct stackframe *frame)
 {
        unsigned long high, low;
        unsigned long fp = frame->fp;
+       unsigned long pc = frame->pc;
+
+       /*
+        * call_with_stack() is the only place we allow SP to jump from one
+        * stack to another, with FP and SP pointing to different stacks,
+        * skipping the FP boundary check at this point.
+        */
+       if (pc >= (unsigned long)&call_with_stack &&
+                       pc < (unsigned long)&call_with_stack_end)
+               return 0;
 
        /* only go to a higher address on the stack */
        low = frame->sp;
        high = ALIGN(low, THREAD_SIZE);
 
-#ifdef CONFIG_CC_IS_CLANG
        /* check current frame pointer is within bounds */
+#ifdef CONFIG_CC_IS_CLANG
        if (fp < low + 4 || fp > high - 4)
                return -EINVAL;
-
-       frame->sp = frame->fp;
-       frame->fp = READ_ONCE_NOCHECK(*(unsigned long *)(fp));
-       frame->pc = READ_ONCE_NOCHECK(*(unsigned long *)(fp + 4));
 #else
-       /* check current frame pointer is within bounds */
        if (fp < low + 12 || fp > high - 4)
                return -EINVAL;
+#endif
+
+       return 0;
+}
+
+int notrace unwind_frame(struct stackframe *frame)
+{
+       unsigned long fp = frame->fp;
+
+       if (frame_pointer_check(frame))
+               return -EINVAL;
 
        /* restore the registers from the stack frame */
+#ifdef CONFIG_CC_IS_CLANG
+       frame->sp = frame->fp;
+       frame->fp = READ_ONCE_NOCHECK(*(unsigned long *)(fp));
+       frame->pc = READ_ONCE_NOCHECK(*(unsigned long *)(fp + 4));
+#else
        frame->fp = READ_ONCE_NOCHECK(*(unsigned long *)(fp - 12));
        frame->sp = READ_ONCE_NOCHECK(*(unsigned long *)(fp - 8));
        frame->pc = READ_ONCE_NOCHECK(*(unsigned long *)(fp - 4));
index 0a268a6..5030d4e 100644 (file)
@@ -46,4 +46,6 @@ UNWIND( .setfp        fpreg, sp       )
        pop     {fpreg, pc}
 UNWIND( .fnend                 )
 #endif
+       .globl call_with_stack_end
+call_with_stack_end:
 ENDPROC(call_with_stack)