Commit 6cea94bb authored by Chen Zhongjin's avatar Chen Zhongjin Committed by openeuler-sync-bot
Browse files

x86/unwind: Fix orc entry for paravirt {save,restore}_fl

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


CVE: NA

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

When CONFIG_PARAVIRT_XXL 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: default avatarChen Zhongjin <chenzhongjin@huawei.com>
Reviewed-by: default avatarXu Kuohai <xukuohai@huawei.com>
(cherry picked from commit cd5fe777)
parent 36771615
Loading
Loading
Loading
Loading
+43 −0
Original line number Diff line number Diff line
@@ -146,6 +146,34 @@ static struct orc_entry orc_fp_entry = {
	.end		= 0,
};

#ifdef CONFIG_PARAVIRT_XXL
static bool check_paravirt(struct unwind_state *state, struct orc_entry *orc)
{
	u8 *ip = (u8 *)state->ip;

	/*
	 * In paravirt_patch.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 == UNWIND_HINT_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;
@@ -425,6 +453,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_XXL
	struct orc_entry para_orc;
#endif

	if (unwind_done(state))
		return false;
@@ -457,6 +488,18 @@ bool unwind_next_frame(struct unwind_state *state)
		state->error = true;
	}

#ifdef CONFIG_PARAVIRT_XXL
	/*
	 * 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 = &para_orc;
	}
#endif

	/* End-of-stack check for kernel threads: */
	if (orc->sp_reg == ORC_REG_UNDEFINED) {
		if (!orc->end)