Commit 5126025c authored by Zheng Yejian's avatar Zheng Yejian
Browse files

arm64: stacktrace: Handle 'lr' in interrupt context

hulk inclusion
category: bugfix
bugzilla: https://gitee.com/openeuler/kernel/issues/IBEMJK


CVE: NA

--------------------------------

Suppose existing call chain: P() -> A() -> B(), B() don't construct stack
frame in which fp and lr are saved for call stack unwinding, then if task1
is interrupted as running at address 'B2' and then preempted by task2,
and task2 unwind the call stack of task1, it expect to see P->A->B, but
actually P->B, A disappeared!

  A():
  A1:  stp fp, lr, ...  <-- suppose fp_P and lr_P saved
  A2:  mov fp, sp       <-- suppose fp_A saved in 'fp' register
  A3:  bl  B            <-- call to B()
  A4:  mov ...          <-- 'A4' saved in 'lr' register

  B():
  B1:  mov ...
  B2:  mov ...   <--  interrupt comes, then run into el1_irq()
  B3:  mov ...   <--  'B3' is saved in 'elr_el1' register

  el1_irq():
  ...          <-- save registers then construct stack frame
  Cm:  bl  arm64_preempt_schedule_irq    <-- Can be preempted here
  Cn:  ...

In this case, at the time interrupt comes, the address 'A4' will be saved
In 'lr' register, then in interrupt entry, 'lr' register will be saved in
Stack memory as struct pt_regs.

See following stack memory layout, as call stack unwinding, if address
'Cn' is found , we know that fp_C is point to pt_regs.stackframe[0],
then we can found the 'A4' in pt_regs.regs[30], then we can know that
B() is currently called by A().

Stack memory (High address downto Low address):

  <High address>
        |-----------------|
        |      lr_P       |
        |-----------------|
        |      fp_P       |
     -> |-----------------|
    |   |      ...        |
    |   |-----------------|
    |   |       B3        |
    |   |-----------------|
     -- |       fp_A      |
     -> |-----------------|  <-- pt_regs.stackframe[0]
    |   |                 |
    |   | X0... fp lr(A4) |  <-- pt_regs.regs[]
    |   |-----------------|
    |   |       ...       |
    |   |-----------------|
    |   |        Cn       |  <-- 'Cn' is return address of
    |   |-----------------|      arm64_preempt_schedule_irq()
     -- |       fp_C      |
        |-----------------|
  <Low address>

Fixes: e429c61d ("livepatch/arm64: Support livepatch without ftrace")
Signed-off-by: default avatarZheng Yejian <zhengyejian1@huawei.com>
parent 908c8608
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -171,4 +171,7 @@ static inline void start_backtrace(struct stackframe *frame,
	frame->prev_type = STACK_TYPE_UNKNOWN;
}

#ifdef CONFIG_PREEMPTION
extern void preempt_schedule_irq_ret_addr(void);
#endif
#endif	/* __ASM_STACKTRACE_H */
+2 −0
Original line number Diff line number Diff line
@@ -523,6 +523,8 @@ alternative_else_nop_endif
#endif
	cbnz	x24, 1f				// preempt count != 0 || NMI return path
	bl	arm64_preempt_schedule_irq	// irq en/disable is done inside
.global preempt_schedule_irq_ret_addr
preempt_schedule_irq_ret_addr:
1:
#endif

+66 −0
Original line number Diff line number Diff line
@@ -129,6 +129,72 @@ void notrace walk_stackframe(struct task_struct *tsk, struct stackframe *frame,

		if (!fn(data, frame->pc))
			break;
#ifdef CONFIG_PREEMPTION
		/*
		 * Suppose existing call chain: P() -> A() -> B(), B() don't construct stack
		 * frame in which fp and lr are saved for call stack unwinding, then if task1
		 * is interrupted as running at address 'B2' and then preempted by task2,
		 * and task2 unwind the call stack of task1, it expect to see P->A->B, but
		 * actually P->B, A disappeared!
		 *
		 *   A():
		 *   A1:  stp fp, lr, ...  <-- suppose fp_P and lr_P saved
		 *   A2:  mov fp, sp       <-- suppose fp_A saved in 'fp' register
		 *   A3:  bl  B            <-- call to B()
		 *   A4:  mov ...          <-- 'A4' saved in 'lr' register
		 *
		 *   B():
		 *   B1:  mov ...
		 *   B2:  mov ...   <--  interrupt comes, then run into el1_irq()
		 *   B3:  mov ...   <--  'B3' is saved in 'elr_el1' register
		 *
		 *   el1_irq():
		 *   ...          <-- save registers then construct stack frame
		 *   Cm:  bl  arm64_preempt_schedule_irq    <-- Can be preempted here
		 *   Cn:  ...
		 *
		 * In this case, at the time interrupt comes, the address 'A4' will be saved
		 * In 'lr' register, then in interrupt entry, 'lr' register will be saved in
		 * Stack memory as struct pt_regs.
		 *
		 * See following stack memory layout, as call stack unwinding, if address
		 * 'Cn' is found , we know that fp_C is point to pt_regs.stackframe[0],
		 * then we can found the 'A4' in pt_regs.regs[30], then we can know that
		 * B() is currently called by A().
		 *
		 * Stack memory (High address downto Low address):
		 *
		 *   <High address>
		 *         |-----------------|
		 *         |      lr_P       |
		 *         |-----------------|
		 *         |      fp_P       |
		 *      -> |-----------------|
		 *     |   |      ...        |
		 *     |   |-----------------|
		 *     |   |       B3        |
		 *     |   |-----------------|
		 *      -- |       fp_A      |
		 *      -> |-----------------|  <-- pt_regs.stackframe[0]
		 *     |   |                 |
		 *     |   | X0... fp lr(A4) |  <-- pt_regs.regs[]
		 *     |   |-----------------|
		 *     |   |       ...       |
		 *     |   |-----------------|
		 *     |   |        Cn       |  <-- 'Cn' is return address of
		 *     |   |-----------------|      arm64_preempt_schedule_irq()
		 *      -- |       fp_C      |
		 *         |-----------------|
		 *   <Low address>
		 */
		if (frame->pc == (unsigned long)preempt_schedule_irq_ret_addr) {
			struct pt_regs *reg = container_of((u64 *)frame->fp,
							   struct pt_regs, stackframe[0]);

			if (!fn(data, reg->regs[30]))
				break;
		}
#endif
		ret = unwind_frame(tsk, frame);
		if (ret < 0)
			break;