On Thu, Jun 18, 2020 at 11:18:23PM +0200, Peter Zijlstra wrote:

> > So maybe also do an untraced cond_local_irq_enable()?  After all, if
> > we’re trying to report a bug from IRQs on, it should be okay to have
> > IRQs on while reporting it. It might even work better than having IRQs
> > off.
> 
> Yes, very good point. Now I want to go look at the old code... I'll frob
> something tomorrow, brain is pretty fried by now.

How's this then?

---
Subject: x86/entry: Fix #UD vs WARN more
From: Peter Zijlstra <pet...@infradead.org>
Date: Tue Jun 16 13:28:36 CEST 2020

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: 15a416e8aaa7 ("x86/entry: Treat BUG/WARN as NMI-like entries")
Signed-off-by: Peter Zijlstra (Intel) <pet...@infradead.org>
---
 arch/x86/kernel/traps.c |   72 +++++++++++++++++++++++++-----------------------
 1 file changed, 38 insertions(+), 34 deletions(-)

--- a/arch/x86/kernel/traps.c
+++ b/arch/x86/kernel/traps.c
@@ -86,15 +86,14 @@ static inline void cond_local_irq_disabl
 
 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(str
                      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.
+        */
+       instrumentation_begin();
+       /*
+        * Since we're emulating a CALL with exceptions, restore the interrupt
+        * state to what it was at the exception site.
         */
-       if (!user_mode(regs) && is_valid_bugaddr(regs->ip)) {
-               enum bug_trap_type type;
+       if (regs->flags & X86_EFLAGS_IF)
+               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_enter();
-               instrumentation_begin();
-               trace_hardirqs_off_finish();
-               type = report_bug(regs->ip, regs);
-               if (regs->flags & X86_EFLAGS_IF)
-                       trace_hardirqs_on_prepare();
-               instrumentation_end();
-               nmi_exit();
-
-               if (type == BUG_TRAP_TYPE_WARN) {
-                       /* Skip the ud2. */
-                       regs->ip += LEN_UD2;
-                       return;
-               }
+       return handled;
+}
 
-               /*
-                * 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.
-                */
-       }
+DEFINE_IDTENTRY_RAW(exc_invalid_op)
+{
+       bool rcu_exit;
+
+       /*
+        * 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();

Reply via email to