Skip to content

Commit 7424fc6

Browse files
gatlinnewhouseKAGA-KOKO
authored andcommitted
x86/traps: Enable UBSAN traps on x86
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 25b8400 ("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 <[email protected]> Signed-off-by: Thomas Gleixner <[email protected]> Acked-by: Peter Zijlstra (Intel) <[email protected]> Cc: Kees Cook <[email protected]> Link: https://lore.kernel.org/all/[email protected] Link: llvm/llvm-project@c5978f42ec8e9#diff-bb68d7cd885f41cfc35843998b0f9f534adb60b415f647109e597ce448e92d9f Link: https://github.com/llvm/llvm-project/blob/main/llvm/lib/Target/X86/X86InstrSystem.td#L27
1 parent 8400291 commit 7424fc6

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)