Skip to content

Commit dea435d

Browse files
committed
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
2 parents 61d1ea9 + 7424fc6 commit dea435d

File tree

4 files changed

+73
-7
lines changed

4 files changed

+73
-7
lines changed

arch/x86/include/asm/bug.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@
1313
#define INSN_UD2 0x0b0f
1414
#define LEN_UD2 2
1515

16+
/*
17+
* In clang we have UD1s reporting UBSAN failures on X86, 64 and 32bit.
18+
*/
19+
#define INSN_ASOP 0x67
20+
#define OPCODE_ESCAPE 0x0f
21+
#define SECOND_BYTE_OPCODE_UD1 0xb9
22+
#define SECOND_BYTE_OPCODE_UD2 0x0b
23+
24+
#define BUG_NONE 0xffff
25+
#define BUG_UD1 0xfffe
26+
#define BUG_UD2 0xfffd
27+
1628
#ifdef CONFIG_GENERIC_BUG
1729

1830
#ifdef CONFIG_X86_32

arch/x86/kernel/traps.c

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include <linux/hardirq.h>
4343
#include <linux/atomic.h>
4444
#include <linux/iommu.h>
45+
#include <linux/ubsan.h>
4546

4647
#include <asm/stacktrace.h>
4748
#include <asm/processor.h>
@@ -91,6 +92,47 @@ __always_inline int is_valid_bugaddr(unsigned long addr)
9192
return *(unsigned short *)addr == INSN_UD2;
9293
}
9394

95+
/*
96+
* Check for UD1 or UD2, accounting for Address Size Override Prefixes.
97+
* If it's a UD1, get the ModRM byte to pass along to UBSan.
98+
*/
99+
__always_inline int decode_bug(unsigned long addr, u32 *imm)
100+
{
101+
u8 v;
102+
103+
if (addr < TASK_SIZE_MAX)
104+
return BUG_NONE;
105+
106+
v = *(u8 *)(addr++);
107+
if (v == INSN_ASOP)
108+
v = *(u8 *)(addr++);
109+
if (v != OPCODE_ESCAPE)
110+
return BUG_NONE;
111+
112+
v = *(u8 *)(addr++);
113+
if (v == SECOND_BYTE_OPCODE_UD2)
114+
return BUG_UD2;
115+
116+
if (!IS_ENABLED(CONFIG_UBSAN_TRAP) || v != SECOND_BYTE_OPCODE_UD1)
117+
return BUG_NONE;
118+
119+
/* Retrieve the immediate (type value) for the UBSAN UD1 */
120+
v = *(u8 *)(addr++);
121+
if (X86_MODRM_RM(v) == 4)
122+
addr++;
123+
124+
*imm = 0;
125+
if (X86_MODRM_MOD(v) == 1)
126+
*imm = *(u8 *)addr;
127+
else if (X86_MODRM_MOD(v) == 2)
128+
*imm = *(u32 *)addr;
129+
else
130+
WARN_ONCE(1, "Unexpected MODRM_MOD: %u\n", X86_MODRM_MOD(v));
131+
132+
return BUG_UD1;
133+
}
134+
135+
94136
static nokprobe_inline int
95137
do_trap_no_signal(struct task_struct *tsk, int trapnr, const char *str,
96138
struct pt_regs *regs, long error_code)
@@ -216,14 +258,17 @@ static inline void handle_invalid_op(struct pt_regs *regs)
216258
static noinstr bool handle_bug(struct pt_regs *regs)
217259
{
218260
bool handled = false;
261+
int ud_type;
262+
u32 imm;
219263

220264
/*
221265
* Normally @regs are unpoisoned by irqentry_enter(), but handle_bug()
222266
* is a rare case that uses @regs without passing them to
223267
* irqentry_enter().
224268
*/
225269
kmsan_unpoison_entry_regs(regs);
226-
if (!is_valid_bugaddr(regs->ip))
270+
ud_type = decode_bug(regs->ip, &imm);
271+
if (ud_type == BUG_NONE)
227272
return handled;
228273

229274
/*
@@ -236,10 +281,14 @@ static noinstr bool handle_bug(struct pt_regs *regs)
236281
*/
237282
if (regs->flags & X86_EFLAGS_IF)
238283
raw_local_irq_enable();
239-
if (report_bug(regs->ip, regs) == BUG_TRAP_TYPE_WARN ||
240-
handle_cfi_failure(regs) == BUG_TRAP_TYPE_WARN) {
241-
regs->ip += LEN_UD2;
242-
handled = true;
284+
if (ud_type == BUG_UD2) {
285+
if (report_bug(regs->ip, regs) == BUG_TRAP_TYPE_WARN ||
286+
handle_cfi_failure(regs) == BUG_TRAP_TYPE_WARN) {
287+
regs->ip += LEN_UD2;
288+
handled = true;
289+
}
290+
} else if (IS_ENABLED(CONFIG_UBSAN_TRAP)) {
291+
pr_crit("%s at %pS\n", report_ubsan_failure(regs, imm), (void *)regs->ip);
243292
}
244293
if (regs->flags & X86_EFLAGS_IF)
245294
raw_local_irq_disable();

include/linux/ubsan.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44

55
#ifdef CONFIG_UBSAN_TRAP
66
const char *report_ubsan_failure(struct pt_regs *regs, u32 check_type);
7+
#else
8+
static inline const char *report_ubsan_failure(struct pt_regs *regs, u32 check_type)
9+
{
10+
return NULL;
11+
}
712
#endif
813

914
#endif

lib/Kconfig.ubsan

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ config UBSAN_TRAP
2929

3030
Also note that selecting Y will cause your kernel to Oops
3131
with an "illegal instruction" error with no further details
32-
when a UBSAN violation occurs. (Except on arm64, which will
33-
report which Sanitizer failed.) This may make it hard to
32+
when a UBSAN violation occurs. (Except on arm64 and x86, which
33+
will report which Sanitizer failed.) This may make it hard to
3434
determine whether an Oops was caused by UBSAN or to figure
3535
out the details of a UBSAN violation. It makes the kernel log
3636
output less useful for bug reports.

0 commit comments

Comments
 (0)