Commit 5b472b6e authored by Peter Zijlstra's avatar Peter Zijlstra
Browse files

x86_64/bug: Implement __WARN_printf()



The basic idea is to have __WARN_printf() be a vararg function such
that the compiler can do the optimal calling convention for us. This
function body will be a #UD and then set up a va_list in the exception
from pt_regs.

But because the trap will be in a called function, the bug_entry must
be passed in. Have that be the first argument, with the format tucked
away inside the bug_entry.

The comments should clarify the real fun details.

The big downside is that all WARNs will now show:

 RIP: 0010:__WARN_trap:+0

One possible solution is to simply discard the top frame when
unwinding. A follow up patch takes care of this slightly differently
by abusing the x86 static_call implementation.

This changes (with the next patches):

	WARN_ONCE(preempt_count() != 2*PREEMPT_DISABLE_OFFSET,
		  "corrupted preempt_count: %s/%d/0x%x\n",

from:

	cmpl    $2, %ecx	#, _7
        jne     .L1472
	...

  .L1472:
        cmpb    $0, __already_done.11(%rip)
        je      .L1513
	...

  .L1513
	movb    $1, __already_done.11(%rip)
	movl    1424(%r14), %edx        # _15->pid, _15->pid
        leaq    1912(%r14), %rsi        #, _17
        movq    $.LC43, %rdi    #,
        call    __warn_printk   #
	ud2
  .pushsection __bug_table,"aw"
        2:
        .long 1b - .    # bug_entry::bug_addr
        .long .LC1 - .  # bug_entry::file
        .word 5093      # bug_entry::line
        .word 2313      # bug_entry::flags
        .org 2b + 12
  .popsection
  .pushsection .discard.annotate_insn,"M", @progbits, 8
        .long 1b - .
        .long 8         # ANNOTYPE_REACHABLE
  .popsection

into:

	cmpl    $2, %ecx        #, _7
        jne     .L1442  #,
	...

  .L1442:
        lea (2f)(%rip), %rdi
  1:
  .pushsection __bug_table,"aw"
        2:
        .long 1b - .    # bug_entry::bug_addr
        .long .LC43 - . # bug_entry::format
        .long .LC1 - .  # bug_entry::file
        .word 5093      # bug_entry::line
        .word 2323      # bug_entry::flags
        .org 2b + 16
  .popsection
        movl    1424(%r14), %edx        # _19->pid, _19->pid
        leaq    1912(%r14), %rsi        #, _13
	ud1 (%edx), %rdi

Notably, by pushing everything into the exception handler it can take
care of the ONCE thing.

Signed-off-by: default avatarPeter Zijlstra (Intel) <peterz@infradead.org>
Link: https://patch.msgid.link/20251110115758.213813530@infradead.org
parent 4f1b701f
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -32,6 +32,14 @@ SYM_FUNC_END(write_ibpb)
/* For KVM */
EXPORT_SYMBOL_GPL(write_ibpb);

SYM_FUNC_START(__WARN_trap)
	ANNOTATE_NOENDBR
	ANNOTATE_REACHABLE
	ud1 (%edx), %_ASM_ARG1
	RET
SYM_FUNC_END(__WARN_trap)
EXPORT_SYMBOL(__WARN_trap)

.popsection

/*
+58 −4
Original line number Diff line number Diff line
@@ -7,6 +7,11 @@
#include <linux/objtool.h>
#include <asm/asm.h>

#ifndef __ASSEMBLY__
struct bug_entry;
extern void __WARN_trap(struct bug_entry *bug, ...);
#endif

/*
 * Despite that some emulators terminate on UD2, we use it for WARN().
 */
@@ -31,6 +36,7 @@
#define BUG_UD2			0xfffe
#define BUG_UD1			0xfffd
#define BUG_UD1_UBSAN		0xfffc
#define BUG_UD1_WARN		0xfffb
#define BUG_UDB			0xffd6
#define BUG_LOCK		0xfff0

@@ -58,14 +64,17 @@
#define __BUG_ENTRY_FORMAT(format)
#endif

#ifdef CONFIG_X86_64
#define HAVE_ARCH_BUG_FORMAT_ARGS
#endif

#define __BUG_ENTRY(format, file, line, flags)				\
	__BUG_REL("1b")		"\t# bug_entry::bug_addr\n"		\
	__BUG_ENTRY_FORMAT(format)					\
	__BUG_ENTRY_VERBOSE(file, line)					\
	"\t.word " flags	"\t# bug_entry::flags\n"

#define _BUG_FLAGS_ASM(ins, format, file, line, flags, size, extra)	\
	"1:\t" ins "\n"							\
#define _BUG_FLAGS_ASM(format, file, line, flags, size, extra)		\
	".pushsection __bug_table,\"aw\"\n\t"				\
	ANNOTATE_DATA_SPECIAL						\
	"2:\n\t"							\
@@ -82,7 +91,8 @@

#define _BUG_FLAGS(cond_str, ins, flags, extra)				\
do {									\
	asm_inline volatile(_BUG_FLAGS_ASM(ins, "%c[fmt]", "%c[file]",	\
	asm_inline volatile("1:\t" ins "\n"				\
			    _BUG_FLAGS_ASM("%c[fmt]", "%c[file]",	\
					   "%c[line]", "%c[fl]",	\
					   "%c[size]", extra)		\
		   : : [fmt] "i" (WARN_CONDITION_STR(cond_str)),	\
@@ -93,7 +103,8 @@ do { \
} while (0)

#define ARCH_WARN_ASM(file, line, flags, size)				\
	_BUG_FLAGS_ASM(ASM_UD2, "0", file, line, flags, size, "")
	"1:\t " ASM_UD2 "\n"						\
	_BUG_FLAGS_ASM("0", file, line, flags, size, "")

#else

@@ -126,6 +137,49 @@ do { \
	instrumentation_end();						\
} while (0)

#ifdef HAVE_ARCH_BUG_FORMAT_ARGS

#ifndef __ASSEMBLY__
struct pt_regs;
struct sysv_va_list { /* from AMD64 System V ABI */
	unsigned int gp_offset;
	unsigned int fp_offset;
	void *overflow_arg_area;
	void *reg_save_area;
};
struct arch_va_list {
	unsigned long regs[6];
	struct sysv_va_list args;
};
extern void *__warn_args(struct arch_va_list *args, struct pt_regs *regs);
#endif /* __ASSEMBLY__ */

#define __WARN_bug_entry(flags, format) ({				\
	struct bug_entry *bug;						\
	asm_inline volatile("lea (2f)(%%rip), %[addr]\n1:\n"		\
			    _BUG_FLAGS_ASM("%c[fmt]", "%c[file]",	\
					   "%c[line]", "%c[fl]",	\
					   "%c[size]", "")		\
		   : [addr] "=r" (bug)					\
		   : [fmt] "i" (format),				\
		     [file] "i" (__FILE__),				\
		     [line] "i" (__LINE__),				\
		     [fl] "i" (flags),					\
		     [size] "i" (sizeof(struct bug_entry)));		\
	bug; })

#define __WARN_print_arg(flags, format, arg...)				\
do {									\
	int __flags = (flags) | BUGFLAG_WARNING | BUGFLAG_ARGS ;	\
	__WARN_trap(__WARN_bug_entry(__flags, format), ## arg);		\
	asm (""); /* inhibit tail-call optimization */			\
} while (0)

#define __WARN_printf(taint, fmt, arg...) \
	__WARN_print_arg(BUGFLAG_TAINT(taint), fmt, ## arg)

#endif /* HAVE_ARCH_BUG_FORMAT_ARGS */

#include <asm-generic/bug.h>

#endif /* _ASM_X86_BUG_H */
+104 −11
Original line number Diff line number Diff line
@@ -102,25 +102,37 @@ __always_inline int is_valid_bugaddr(unsigned long addr)
 * UBSan{0}:     67 0f b9 00             ud1    (%eax),%eax
 * UBSan{10}:    67 0f b9 40 10          ud1    0x10(%eax),%eax
 * static_call:  0f b9 cc                ud1    %esp,%ecx
 * __WARN_trap:  67 48 0f b9 3a          ud1    (%edx),%reg
 *
 * Notably UBSAN uses EAX, static_call uses ECX.
 * Notable, since __WARN_trap can use all registers, the distinction between
 * UD1 users is through R/M.
 */
__always_inline int decode_bug(unsigned long addr, s32 *imm, int *len)
{
	unsigned long start = addr;
	u8 v, reg, rm, rex = 0;
	int type = BUG_UD1;
	bool lock = false;
	u8 v;

	if (addr < TASK_SIZE_MAX)
		return BUG_NONE;

	for (;;) {
		v = *(u8 *)(addr++);
		if (v == INSN_ASOP)
		v = *(u8 *)(addr++);
			continue;

		if (v == INSN_LOCK) {
			lock = true;
		v = *(u8 *)(addr++);
			continue;
		}

		if ((v & 0xf0) == 0x40) {
			rex = v;
			continue;
		}

		break;
	}

	switch (v) {
@@ -156,18 +168,33 @@ __always_inline int decode_bug(unsigned long addr, s32 *imm, int *len)
	if (X86_MODRM_MOD(v) != 3 && X86_MODRM_RM(v) == 4)
		addr++;			/* SIB */

	reg = X86_MODRM_REG(v) + 8*!!X86_REX_R(rex);
	rm  = X86_MODRM_RM(v)  + 8*!!X86_REX_B(rex);

	/* Decode immediate, if present */
	switch (X86_MODRM_MOD(v)) {
	case 0: if (X86_MODRM_RM(v) == 5)
			addr += 4;	/* RIP + disp32 */

		if (rm == 0)		/* (%eax) */
			type = BUG_UD1_UBSAN;

		if (rm == 2) {		/* (%edx) */
			*imm = reg;
			type = BUG_UD1_WARN;
		}
		break;

	case 1: *imm = *(s8 *)addr;
		addr += 1;
		if (rm == 0)		/* (%eax) */
			type = BUG_UD1_UBSAN;
		break;

	case 2: *imm = *(s32 *)addr;
		addr += 4;
		if (rm == 0)		/* (%eax) */
			type = BUG_UD1_UBSAN;
		break;

	case 3: break;
@@ -176,12 +203,73 @@ __always_inline int decode_bug(unsigned long addr, s32 *imm, int *len)
	/* record instruction length */
	*len = addr - start;

	if (X86_MODRM_REG(v) == 0)	/* EAX */
		return BUG_UD1_UBSAN;
	return type;
}

	return BUG_UD1;
static inline unsigned long pt_regs_val(struct pt_regs *regs, int nr)
{
	int offset = pt_regs_offset(regs, nr);
	if (WARN_ON_ONCE(offset < -0))
		return 0;
	return *((unsigned long *)((void *)regs + offset));
}

#ifdef HAVE_ARCH_BUG_FORMAT_ARGS
/*
 * Create a va_list from an exception context.
 */
void *__warn_args(struct arch_va_list *args, struct pt_regs *regs)
{
	/*
	 * Register save area; populate with function call argument registers
	 */
	args->regs[0] = regs->di;
	args->regs[1] = regs->si;
	args->regs[2] = regs->dx;
	args->regs[3] = regs->cx;
	args->regs[4] = regs->r8;
	args->regs[5] = regs->r9;

	/*
	 * From the ABI document:
	 *
	 * @gp_offset - the element holds the offset in bytes from
	 * reg_save_area to the place where the next available general purpose
	 * argument register is saved. In case all argument registers have
	 * been exhausted, it is set to the value 48 (6*8).
	 *
	 * @fp_offset - the element holds the offset in bytes from
	 * reg_save_area to the place where the next available floating point
	 * argument is saved. In case all argument registers have been
	 * exhausted, it is set to the value 176 (6*8 + 8*16)
	 *
	 * @overflow_arg_area - this pointer is used to fetch arguments passed
	 * on the stack. It is initialized with the address of the first
	 * argument passed on the stack, if any, and then always updated to
	 * point to the start of the next argument on the stack.
	 *
	 * @reg_save_area - the element points to the start of the register
	 * save area.
	 *
	 * Notably the vararg starts with the second argument and there are no
	 * floating point arguments in the kernel.
	 */
	args->args.gp_offset = 1*8;
	args->args.fp_offset = 6*8 + 8*16;
	args->args.reg_save_area = &args->regs;
	args->args.overflow_arg_area = (void *)regs->sp;

	/*
	 * If the exception came from __WARN_trap, there is a return
	 * address on the stack, skip that. This is why any __WARN_trap()
	 * caller must inhibit tail-call optimization.
	 */
	if ((void *)regs->ip == &__WARN_trap)
		args->args.overflow_arg_area += 8;

	return &args->args;
}
#endif /* HAVE_ARCH_BUG_FORMAT */

static nokprobe_inline int
do_trap_no_signal(struct task_struct *tsk, int trapnr, const char *str,
@@ -334,6 +422,11 @@ static noinstr bool handle_bug(struct pt_regs *regs)
		raw_local_irq_enable();

	switch (ud_type) {
	case BUG_UD1_WARN:
		if (report_bug_entry((void *)pt_regs_val(regs, ud_imm), regs) == BUG_TRAP_TYPE_WARN)
			handled = true;
		break;

	case BUG_UD2:
		if (report_bug(regs->ip, regs) == BUG_TRAP_TYPE_WARN) {
			handled = true;