Commit dea435d3 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'x86-core-2024-09-17' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull x86 core update from Thomas Gleixner:
 "Enable UBSAN traps for x86, which provides better reporting through
  metadata encodeded into UD1"

* tag 'x86-core-2024-09-17' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  x86/traps: Enable UBSAN traps on x86
parents 61d1ea91 7424fc6b
...@@ -13,6 +13,18 @@ ...@@ -13,6 +13,18 @@
#define INSN_UD2 0x0b0f #define INSN_UD2 0x0b0f
#define LEN_UD2 2 #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_GENERIC_BUG
#ifdef CONFIG_X86_32 #ifdef CONFIG_X86_32
......
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
#include <linux/hardirq.h> #include <linux/hardirq.h>
#include <linux/atomic.h> #include <linux/atomic.h>
#include <linux/iommu.h> #include <linux/iommu.h>
#include <linux/ubsan.h>
#include <asm/stacktrace.h> #include <asm/stacktrace.h>
#include <asm/processor.h> #include <asm/processor.h>
...@@ -91,6 +92,47 @@ __always_inline int is_valid_bugaddr(unsigned long addr) ...@@ -91,6 +92,47 @@ __always_inline int is_valid_bugaddr(unsigned long addr)
return *(unsigned short *)addr == INSN_UD2; 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 static nokprobe_inline int
do_trap_no_signal(struct task_struct *tsk, int trapnr, const char *str, do_trap_no_signal(struct task_struct *tsk, int trapnr, const char *str,
struct pt_regs *regs, long error_code) struct pt_regs *regs, long error_code)
...@@ -216,6 +258,8 @@ static inline void handle_invalid_op(struct pt_regs *regs) ...@@ -216,6 +258,8 @@ static inline void handle_invalid_op(struct pt_regs *regs)
static noinstr bool handle_bug(struct pt_regs *regs) static noinstr bool handle_bug(struct pt_regs *regs)
{ {
bool handled = false; bool handled = false;
int ud_type;
u32 imm;
/* /*
* Normally @regs are unpoisoned by irqentry_enter(), but handle_bug() * Normally @regs are unpoisoned by irqentry_enter(), but handle_bug()
...@@ -223,7 +267,8 @@ static noinstr bool handle_bug(struct pt_regs *regs) ...@@ -223,7 +267,8 @@ static noinstr bool handle_bug(struct pt_regs *regs)
* irqentry_enter(). * irqentry_enter().
*/ */
kmsan_unpoison_entry_regs(regs); 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; return handled;
/* /*
...@@ -236,11 +281,15 @@ static noinstr bool handle_bug(struct pt_regs *regs) ...@@ -236,11 +281,15 @@ static noinstr bool handle_bug(struct pt_regs *regs)
*/ */
if (regs->flags & X86_EFLAGS_IF) if (regs->flags & X86_EFLAGS_IF)
raw_local_irq_enable(); raw_local_irq_enable();
if (ud_type == BUG_UD2) {
if (report_bug(regs->ip, regs) == BUG_TRAP_TYPE_WARN || if (report_bug(regs->ip, regs) == BUG_TRAP_TYPE_WARN ||
handle_cfi_failure(regs) == BUG_TRAP_TYPE_WARN) { handle_cfi_failure(regs) == BUG_TRAP_TYPE_WARN) {
regs->ip += LEN_UD2; regs->ip += LEN_UD2;
handled = true; 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) if (regs->flags & X86_EFLAGS_IF)
raw_local_irq_disable(); raw_local_irq_disable();
instrumentation_end(); instrumentation_end();
......
...@@ -4,6 +4,11 @@ ...@@ -4,6 +4,11 @@
#ifdef CONFIG_UBSAN_TRAP #ifdef CONFIG_UBSAN_TRAP
const char *report_ubsan_failure(struct pt_regs *regs, u32 check_type); 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
#endif #endif
...@@ -29,8 +29,8 @@ config UBSAN_TRAP ...@@ -29,8 +29,8 @@ config UBSAN_TRAP
Also note that selecting Y will cause your kernel to Oops Also note that selecting Y will cause your kernel to Oops
with an "illegal instruction" error with no further details with an "illegal instruction" error with no further details
when a UBSAN violation occurs. (Except on arm64, which will when a UBSAN violation occurs. (Except on arm64 and x86, which
report which Sanitizer failed.) This may make it hard to will report which Sanitizer failed.) This may make it hard to
determine whether an Oops was caused by UBSAN or to figure determine whether an Oops was caused by UBSAN or to figure
out the details of a UBSAN violation. It makes the kernel log out the details of a UBSAN violation. It makes the kernel log
output less useful for bug reports. output less useful for bug reports.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment