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: e429c61d12bf ("livepatch/arm64: Support livepatch without ftrace") Signed-off-by: Zheng Yejian zhengyejian1@huawei.com --- arch/arm64/include/asm/stacktrace.h | 3 ++ arch/arm64/kernel/entry.S | 2 + arch/arm64/kernel/stacktrace.c | 66 +++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+)
diff --git a/arch/arm64/include/asm/stacktrace.h b/arch/arm64/include/asm/stacktrace.h index eb29b1fe8255..dda36f917263 100644 --- a/arch/arm64/include/asm/stacktrace.h +++ b/arch/arm64/include/asm/stacktrace.h @@ -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 */ diff --git a/arch/arm64/kernel/entry.S b/arch/arm64/kernel/entry.S index eb4ba8308397..1290f36c8371 100644 --- a/arch/arm64/kernel/entry.S +++ b/arch/arm64/kernel/entry.S @@ -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
diff --git a/arch/arm64/kernel/stacktrace.c b/arch/arm64/kernel/stacktrace.c index 2073a3a7fe75..93ac3c74fb41 100644 --- a/arch/arm64/kernel/stacktrace.c +++ b/arch/arm64/kernel/stacktrace.c @@ -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;
反馈: 您发送到kernel@openeuler.org的补丁/补丁集,已成功转换为PR! PR链接地址: https://gitee.com/openeuler/kernel/pulls/14392 邮件列表地址:https://mailweb.openeuler.org/hyperkitty/list/kernel@openeuler.org/message/O...
FeedBack: The patch(es) which you have sent to kernel@openeuler.org mailing list has been converted to a pull request successfully! Pull request link: https://gitee.com/openeuler/kernel/pulls/14392 Mailing list address: https://mailweb.openeuler.org/hyperkitty/list/kernel@openeuler.org/message/O...