From: Chen Zhongjin chenzhongjin@huawei.com
hulk inclusion category: bugfix bugzilla: 187818, https://gitee.com/openeuler/kernel/issues/I6DK3O CVE: NA
--------------------------------
When CONFIG_PARAVIRT is enabled, the code of {save,restore}_fl is defined as:
ff 14 25 00 00 00 00 callq *0x0
which will be patched to call the xen paravirt function, or native implementation, in 'paravirt_patch_64.c':
pushfq; popq %rax // for native_save_fl pushq %rdi; popfq // for native_restore_fl
The orc metadata is generated with insn 'callq', so it can become inconsistent with the real insn 'push;pop'. This makes stacktrace on the 'pop' insn fail and incorrect stacktrace result can be returned.
To prevent reliable stacktrace broken, check the insns when unwind pt_regs stack frame:
When there are 'push;pop' combination and both insns don't change orc entry, it means the stack state is inconsistent with orc on pop. Add one slot to sp_offset for on original orc entry to get the correct orc entry.
Signed-off-by: Chen Zhongjin chenzhongjin@huawei.com Reviewed-by: Xu Kuohai xukuohai@huawei.com Signed-off-by: Yongqiang Liu liuyongqiang13@huawei.com --- arch/x86/kernel/unwind_orc.c | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+)
diff --git a/arch/x86/kernel/unwind_orc.c b/arch/x86/kernel/unwind_orc.c index 0402bc0b5851..ec4a14e4f639 100644 --- a/arch/x86/kernel/unwind_orc.c +++ b/arch/x86/kernel/unwind_orc.c @@ -138,6 +138,34 @@ static struct orc_entry null_orc_entry = { .type = ORC_TYPE_CALL };
+#ifdef CONFIG_PARAVIRT +static bool check_paravirt(struct unwind_state *state, struct orc_entry *orc) +{ + u8 *ip = (u8 *)state->ip; + + /* + * In paravirt_patch_64.c, patched paravirt opcode should be: + * pushfq; popq %rax // 0x9c 0x58 + * pushq %rdi; popfq // 0x57 0x9d + * + * Error unwinding only happens when: + * 1. In irq or preempt context. + * 2. Current insn is popq, and it doesn't change orc. + * 3. Last insn doesn't change orc, checking it first to + * promise ip - 1 is valid. + * 4. Last byte fits pushf. + */ + if (state->regs && orc->type == ORC_TYPE_CALL && + (ip[0] == 0x58 || ip[0] == 0x9d) && + orc == orc_find((unsigned long)(ip + 1)) && + orc == orc_find((unsigned long)(ip - 1)) && + (ip[-1] == 0x9c || ip[-1] == 0x57)) + return true; + + return false; +} +#endif + static struct orc_entry *orc_find(unsigned long ip) { static struct orc_entry *orc; @@ -415,6 +443,9 @@ bool unwind_next_frame(struct unwind_state *state) enum stack_type prev_type = state->stack_info.type; struct orc_entry *orc; bool indirect = false; +#ifdef CONFIG_PARAVIRT + struct orc_entry para_orc; +#endif
if (unwind_done(state)) return false; @@ -436,6 +467,18 @@ bool unwind_next_frame(struct unwind_state *state) if (!orc) goto err;
+#ifdef CONFIG_PARAVIRT + /* + * When hitting paravirt POP insn, the orc entry should add + * one slot for PUSH insn. + */ + if (!state->error && check_paravirt(state, orc)) { + para_orc = *orc; + para_orc.sp_offset += sizeof(long); + orc = ¶_orc; + } +#endif + /* End-of-stack check for kernel threads: */ if (orc->sp_reg == ORC_REG_UNDEFINED) { if (!orc->end)