]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
x86/traps: Enable UBSAN traps on x86
authorGatlin Newhouse <gatlin.newhouse@gmail.com>
Wed, 24 Jul 2024 00:01:55 +0000 (00:01 +0000)
committerThomas Gleixner <tglx@linutronix.de>
Tue, 6 Aug 2024 11:42:40 +0000 (13:42 +0200)
Currently ARM64 extracts which specific sanitizer has caused a trap via
encoded data in the trap instruction. Clang on x86 currently encodes the
same data in the UD1 instruction but x86 handle_bug() and
is_valid_bugaddr() currently only look at UD2.

Bring x86 to parity with ARM64, similar to commit 25b84002afb9 ("arm64:
Support Clang UBSAN trap codes for better reporting"). See the llvm
links for information about the code generation.

Enable the reporting of UBSAN sanitizer details on x86 compiled with clang
when CONFIG_UBSAN_TRAP=y by analysing UD1 and retrieving the type immediate
which is encoded by the compiler after the UD1.

[ tglx: Simplified it by moving the printk() into handle_bug() ]

Signed-off-by: Gatlin Newhouse <gatlin.newhouse@gmail.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Cc: Kees Cook <keescook@chromium.org>
Link: https://lore.kernel.org/all/20240724000206.451425-1-gatlin.newhouse@gmail.com
Link: https://github.com/llvm/llvm-project/commit/c5978f42ec8e9#diff-bb68d7cd885f41cfc35843998b0f9f534adb60b415f647109e597ce448e92d9f
Link: https://github.com/llvm/llvm-project/blob/main/llvm/lib/Target/X86/X86InstrSystem.td#L27
arch/x86/include/asm/bug.h
arch/x86/kernel/traps.c
include/linux/ubsan.h
lib/Kconfig.ubsan

index a3ec87d198ac8398309e2962b5bcc59eeb074692..806649c7f23dc627d4680fb0f64a7312468b14a3 100644 (file)
 #define INSN_UD2       0x0b0f
 #define LEN_UD2                2
 
+/*
+ * In clang we have UD1s reporting UBSAN failures on X86, 64 and 32bit.
+ */
+#define INSN_ASOP              0x67
+#define OPCODE_ESCAPE          0x0f
+#define SECOND_BYTE_OPCODE_UD1 0xb9
+#define SECOND_BYTE_OPCODE_UD2 0x0b
+
+#define BUG_NONE               0xffff
+#define BUG_UD1                        0xfffe
+#define BUG_UD2                        0xfffd
+
 #ifdef CONFIG_GENERIC_BUG
 
 #ifdef CONFIG_X86_32
index 4fa0b17e5043aa81070fe7716cda5518f808957c..415881607c5df1277d6d699e1c80ac71f624aa7f 100644 (file)
@@ -42,6 +42,7 @@
 #include <linux/hardirq.h>
 #include <linux/atomic.h>
 #include <linux/iommu.h>
+#include <linux/ubsan.h>
 
 #include <asm/stacktrace.h>
 #include <asm/processor.h>
@@ -91,6 +92,47 @@ __always_inline int is_valid_bugaddr(unsigned long addr)
        return *(unsigned short *)addr == INSN_UD2;
 }
 
+/*
+ * Check for UD1 or UD2, accounting for Address Size Override Prefixes.
+ * If it's a UD1, get the ModRM byte to pass along to UBSan.
+ */
+__always_inline int decode_bug(unsigned long addr, u32 *imm)
+{
+       u8 v;
+
+       if (addr < TASK_SIZE_MAX)
+               return BUG_NONE;
+
+       v = *(u8 *)(addr++);
+       if (v == INSN_ASOP)
+               v = *(u8 *)(addr++);
+       if (v != OPCODE_ESCAPE)
+               return BUG_NONE;
+
+       v = *(u8 *)(addr++);
+       if (v == SECOND_BYTE_OPCODE_UD2)
+               return BUG_UD2;
+
+       if (!IS_ENABLED(CONFIG_UBSAN_TRAP) || v != SECOND_BYTE_OPCODE_UD1)
+               return BUG_NONE;
+
+       /* Retrieve the immediate (type value) for the UBSAN UD1 */
+       v = *(u8 *)(addr++);
+       if (X86_MODRM_RM(v) == 4)
+               addr++;
+
+       *imm = 0;
+       if (X86_MODRM_MOD(v) == 1)
+               *imm = *(u8 *)addr;
+       else if (X86_MODRM_MOD(v) == 2)
+               *imm = *(u32 *)addr;
+       else
+               WARN_ONCE(1, "Unexpected MODRM_MOD: %u\n", X86_MODRM_MOD(v));
+
+       return BUG_UD1;
+}
+
+
 static nokprobe_inline int
 do_trap_no_signal(struct task_struct *tsk, int trapnr, const char *str,
                  struct pt_regs *regs, long error_code)
@@ -216,6 +258,8 @@ static inline void handle_invalid_op(struct pt_regs *regs)
 static noinstr bool handle_bug(struct pt_regs *regs)
 {
        bool handled = false;
+       int ud_type;
+       u32 imm;
 
        /*
         * Normally @regs are unpoisoned by irqentry_enter(), but handle_bug()
@@ -223,7 +267,8 @@ static noinstr bool handle_bug(struct pt_regs *regs)
         * irqentry_enter().
         */
        kmsan_unpoison_entry_regs(regs);
-       if (!is_valid_bugaddr(regs->ip))
+       ud_type = decode_bug(regs->ip, &imm);
+       if (ud_type == BUG_NONE)
                return handled;
 
        /*
@@ -236,10 +281,14 @@ static noinstr bool handle_bug(struct pt_regs *regs)
         */
        if (regs->flags & X86_EFLAGS_IF)
                raw_local_irq_enable();
-       if (report_bug(regs->ip, regs) == BUG_TRAP_TYPE_WARN ||
-           handle_cfi_failure(regs) == BUG_TRAP_TYPE_WARN) {
-               regs->ip += LEN_UD2;
-               handled = true;
+       if (ud_type == BUG_UD2) {
+               if (report_bug(regs->ip, regs) == BUG_TRAP_TYPE_WARN ||
+                   handle_cfi_failure(regs) == BUG_TRAP_TYPE_WARN) {
+                       regs->ip += LEN_UD2;
+                       handled = true;
+               }
+       } else if (IS_ENABLED(CONFIG_UBSAN_TRAP)) {
+               pr_crit("%s at %pS\n", report_ubsan_failure(regs, imm), (void *)regs->ip);
        }
        if (regs->flags & X86_EFLAGS_IF)
                raw_local_irq_disable();
index bff7445498ded45f3b92cfc68658cf2901bcff99..d8219cbe09ff8d4d504bff36b74cf72f44e4a4c1 100644 (file)
@@ -4,6 +4,11 @@
 
 #ifdef CONFIG_UBSAN_TRAP
 const char *report_ubsan_failure(struct pt_regs *regs, u32 check_type);
+#else
+static inline const char *report_ubsan_failure(struct pt_regs *regs, u32 check_type)
+{
+       return NULL;
+}
 #endif
 
 #endif
index bdda600f8dfbebd7e468527e190d2686a269ef89..1d4aa7a83b3a55ee9fc3612ceb1c1f157c2dafb7 100644 (file)
@@ -29,8 +29,8 @@ config UBSAN_TRAP
 
          Also note that selecting Y will cause your kernel to Oops
          with an "illegal instruction" error with no further details
-         when a UBSAN violation occurs. (Except on arm64, which will
-         report which Sanitizer failed.) This may make it hard to
+         when a UBSAN violation occurs. (Except on arm64 and x86, which
+         will report which Sanitizer failed.) This may make it hard to
          determine whether an Oops was caused by UBSAN or to figure
          out the details of a UBSAN violation. It makes the kernel log
          output less useful for bug reports.