Commit 145a773a authored by Peter Zijlstra's avatar Peter Zijlstra
Browse files

x86/entry: Fix #UD vs WARN more



vmlinux.o: warning: objtool: exc_invalid_op()+0x47: call to probe_kernel_read() leaves .noinstr.text section

Since we use UD2 as a short-cut for 'CALL __WARN', treat it as such.
Have the bare exception handler do the report_bug() thing.

Fixes: 15a416e8 ("x86/entry: Treat BUG/WARN as NMI-like entries")
Signed-off-by: default avatarPeter Zijlstra (Intel) <peterz@infradead.org>
Acked-by: default avatarAndy Lutomirski <luto@kernel.org>
Link: https://lkml.kernel.org/r/20200622114713.GE577403@hirez.programming.kicks-ass.net
parent c7aadc09
Loading
Loading
Loading
Loading
+38 −34
Original line number Diff line number Diff line
@@ -84,17 +84,16 @@ static inline void cond_local_irq_disable(struct pt_regs *regs)
		local_irq_disable();
}

int is_valid_bugaddr(unsigned long addr)
__always_inline int is_valid_bugaddr(unsigned long addr)
{
	unsigned short ud;

	if (addr < TASK_SIZE_MAX)
		return 0;

	if (probe_kernel_address((unsigned short *)addr, ud))
		return 0;

	return ud == INSN_UD0 || ud == INSN_UD2;
	/*
	 * We got #UD, if the text isn't readable we'd have gotten
	 * a different exception.
	 */
	return *(unsigned short *)addr == INSN_UD2;
}

static nokprobe_inline int
@@ -216,40 +215,45 @@ static inline void handle_invalid_op(struct pt_regs *regs)
		      ILL_ILLOPN, error_get_trap_addr(regs));
}

DEFINE_IDTENTRY_RAW(exc_invalid_op)
static noinstr bool handle_bug(struct pt_regs *regs)
{
	bool rcu_exit;
	bool handled = false;

	if (!is_valid_bugaddr(regs->ip))
		return handled;

	/*
	 * Handle BUG/WARN like NMIs instead of like normal idtentries:
	 * if we bugged/warned in a bad RCU context, for example, the last
	 * thing we want is to BUG/WARN again in the idtentry code, ad
	 * infinitum.
	 * All lies, just get the WARN/BUG out.
	 */
	if (!user_mode(regs) && is_valid_bugaddr(regs->ip)) {
		enum bug_trap_type type;

		nmi_enter();
	instrumentation_begin();
		trace_hardirqs_off_finish();
		type = report_bug(regs->ip, regs);
	/*
	 * Since we're emulating a CALL with exceptions, restore the interrupt
	 * state to what it was at the exception site.
	 */
	if (regs->flags & X86_EFLAGS_IF)
			trace_hardirqs_on_prepare();
		raw_local_irq_enable();
	if (report_bug(regs->ip, regs) == BUG_TRAP_TYPE_WARN) {
		regs->ip += LEN_UD2;
		handled = true;
	}
	if (regs->flags & X86_EFLAGS_IF)
		raw_local_irq_disable();
	instrumentation_end();
		nmi_exit();

		if (type == BUG_TRAP_TYPE_WARN) {
			/* Skip the ud2. */
			regs->ip += LEN_UD2;
			return;
	return handled;
}

DEFINE_IDTENTRY_RAW(exc_invalid_op)
{
	bool rcu_exit;

	/*
		 * Else, if this was a BUG and report_bug returns or if this
		 * was just a normal #UD, we want to continue onward and
		 * crash.
	 * We use UD2 as a short encoding for 'CALL __WARN', as such
	 * handle it before exception entry to avoid recursive WARN
	 * in case exception entry is the one triggering WARNs.
	 */
	}
	if (!user_mode(regs) && handle_bug(regs))
		return;

	rcu_exit = idtentry_enter_cond_rcu(regs);
	instrumentation_begin();