Commit f35e4663 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'x86-int80-20231207' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull x86 int80 fixes from Dave Hansen:
 "Avoid VMM misuse of 'int 0x80' handling in TDX and SEV guests.

  It also has the very nice side effect of getting rid of a bunch of
  assembly entry code"

* tag 'x86-int80-20231207' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  x86/tdx: Allow 32-bit emulation by default
  x86/entry: Do not allow external 0x80 interrupts
  x86/entry: Convert INT 0x80 emulation to IDTENTRY
  x86/coco: Disable 32-bit emulation by default on TDX and SEV
parents 55b224d9 f4116bfc
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@
#include <asm/coco.h>
#include <asm/tdx.h>
#include <asm/vmx.h>
#include <asm/ia32.h>
#include <asm/insn.h>
#include <asm/insn-eval.h>
#include <asm/pgtable.h>
+92 −1
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@
#include <xen/events.h>
#endif

#include <asm/apic.h>
#include <asm/desc.h>
#include <asm/traps.h>
#include <asm/vdso.h>
@@ -167,7 +168,96 @@ static __always_inline void do_syscall_32_irqs_on(struct pt_regs *regs, int nr)
	}
}

/* Handles int $0x80 */
#ifdef CONFIG_IA32_EMULATION
static __always_inline bool int80_is_external(void)
{
	const unsigned int offs = (0x80 / 32) * 0x10;
	const u32 bit = BIT(0x80 % 32);

	/* The local APIC on XENPV guests is fake */
	if (cpu_feature_enabled(X86_FEATURE_XENPV))
		return false;

	/*
	 * If vector 0x80 is set in the APIC ISR then this is an external
	 * interrupt. Either from broken hardware or injected by a VMM.
	 *
	 * Note: In guest mode this is only valid for secure guests where
	 * the secure module fully controls the vAPIC exposed to the guest.
	 */
	return apic_read(APIC_ISR + offs) & bit;
}

/**
 * int80_emulation - 32-bit legacy syscall entry
 *
 * This entry point can be used by 32-bit and 64-bit programs to perform
 * 32-bit system calls.  Instances of INT $0x80 can be found inline in
 * various programs and libraries.  It is also used by the vDSO's
 * __kernel_vsyscall fallback for hardware that doesn't support a faster
 * entry method.  Restarted 32-bit system calls also fall back to INT
 * $0x80 regardless of what instruction was originally used to do the
 * system call.
 *
 * This is considered a slow path.  It is not used by most libc
 * implementations on modern hardware except during process startup.
 *
 * The arguments for the INT $0x80 based syscall are on stack in the
 * pt_regs structure:
 *   eax:				system call number
 *   ebx, ecx, edx, esi, edi, ebp:	arg1 - arg 6
 */
DEFINE_IDTENTRY_RAW(int80_emulation)
{
	int nr;

	/* Kernel does not use INT $0x80! */
	if (unlikely(!user_mode(regs))) {
		irqentry_enter(regs);
		instrumentation_begin();
		panic("Unexpected external interrupt 0x80\n");
	}

	/*
	 * Establish kernel context for instrumentation, including for
	 * int80_is_external() below which calls into the APIC driver.
	 * Identical for soft and external interrupts.
	 */
	enter_from_user_mode(regs);

	instrumentation_begin();
	add_random_kstack_offset();

	/* Validate that this is a soft interrupt to the extent possible */
	if (unlikely(int80_is_external()))
		panic("Unexpected external interrupt 0x80\n");

	/*
	 * The low level idtentry code pushed -1 into regs::orig_ax
	 * and regs::ax contains the syscall number.
	 *
	 * User tracing code (ptrace or signal handlers) might assume
	 * that the regs::orig_ax contains a 32-bit number on invoking
	 * a 32-bit syscall.
	 *
	 * Establish the syscall convention by saving the 32bit truncated
	 * syscall number in regs::orig_ax and by invalidating regs::ax.
	 */
	regs->orig_ax = regs->ax & GENMASK(31, 0);
	regs->ax = -ENOSYS;

	nr = syscall_32_enter(regs);

	local_irq_enable();
	nr = syscall_enter_from_user_mode_work(regs, nr);
	do_syscall_32_irqs_on(regs, nr);

	instrumentation_end();
	syscall_exit_to_user_mode(regs);
}
#else /* CONFIG_IA32_EMULATION */

/* Handles int $0x80 on a 32bit kernel */
__visible noinstr void do_int80_syscall_32(struct pt_regs *regs)
{
	int nr = syscall_32_enter(regs);
@@ -186,6 +276,7 @@ __visible noinstr void do_int80_syscall_32(struct pt_regs *regs)
	instrumentation_end();
	syscall_exit_to_user_mode(regs);
}
#endif /* !CONFIG_IA32_EMULATION */

static noinstr bool __do_fast_syscall_32(struct pt_regs *regs)
{
+0 −77
Original line number Diff line number Diff line
@@ -275,80 +275,3 @@ SYM_INNER_LABEL(entry_SYSRETL_compat_end, SYM_L_GLOBAL)
	ANNOTATE_NOENDBR
	int3
SYM_CODE_END(entry_SYSCALL_compat)

/*
 * 32-bit legacy system call entry.
 *
 * 32-bit x86 Linux system calls traditionally used the INT $0x80
 * instruction.  INT $0x80 lands here.
 *
 * This entry point can be used by 32-bit and 64-bit programs to perform
 * 32-bit system calls.  Instances of INT $0x80 can be found inline in
 * various programs and libraries.  It is also used by the vDSO's
 * __kernel_vsyscall fallback for hardware that doesn't support a faster
 * entry method.  Restarted 32-bit system calls also fall back to INT
 * $0x80 regardless of what instruction was originally used to do the
 * system call.
 *
 * This is considered a slow path.  It is not used by most libc
 * implementations on modern hardware except during process startup.
 *
 * Arguments:
 * eax  system call number
 * ebx  arg1
 * ecx  arg2
 * edx  arg3
 * esi  arg4
 * edi  arg5
 * ebp  arg6
 */
SYM_CODE_START(entry_INT80_compat)
	UNWIND_HINT_ENTRY
	ENDBR
	/*
	 * Interrupts are off on entry.
	 */
	ASM_CLAC			/* Do this early to minimize exposure */
	ALTERNATIVE "swapgs", "", X86_FEATURE_XENPV

	/*
	 * User tracing code (ptrace or signal handlers) might assume that
	 * the saved RAX contains a 32-bit number when we're invoking a 32-bit
	 * syscall.  Just in case the high bits are nonzero, zero-extend
	 * the syscall number.  (This could almost certainly be deleted
	 * with no ill effects.)
	 */
	movl	%eax, %eax

	/* switch to thread stack expects orig_ax and rdi to be pushed */
	pushq	%rax			/* pt_regs->orig_ax */

	/* Need to switch before accessing the thread stack. */
	SWITCH_TO_KERNEL_CR3 scratch_reg=%rax

	/* In the Xen PV case we already run on the thread stack. */
	ALTERNATIVE "", "jmp .Lint80_keep_stack", X86_FEATURE_XENPV

	movq	%rsp, %rax
	movq	PER_CPU_VAR(pcpu_hot + X86_top_of_stack), %rsp

	pushq	5*8(%rax)		/* regs->ss */
	pushq	4*8(%rax)		/* regs->rsp */
	pushq	3*8(%rax)		/* regs->eflags */
	pushq	2*8(%rax)		/* regs->cs */
	pushq	1*8(%rax)		/* regs->ip */
	pushq	0*8(%rax)		/* regs->orig_ax */
.Lint80_keep_stack:

	PUSH_AND_CLEAR_REGS rax=$-ENOSYS
	UNWIND_HINT_REGS

	cld

	IBRS_ENTER
	UNTRAIN_RET

	movq	%rsp, %rdi
	call	do_int80_syscall_32
	jmp	swapgs_restore_regs_and_return_to_usermode
SYM_CODE_END(entry_INT80_compat)
+7 −0
Original line number Diff line number Diff line
@@ -75,6 +75,11 @@ static inline bool ia32_enabled(void)
	return __ia32_enabled;
}

static inline void ia32_disable(void)
{
	__ia32_enabled = false;
}

#else /* !CONFIG_IA32_EMULATION */

static inline bool ia32_enabled(void)
@@ -82,6 +87,8 @@ static inline bool ia32_enabled(void)
	return IS_ENABLED(CONFIG_X86_32);
}

static inline void ia32_disable(void) {}

#endif

#endif /* _ASM_X86_IA32_H */
+4 −0
Original line number Diff line number Diff line
@@ -569,6 +569,10 @@ DECLARE_IDTENTRY_RAW(X86_TRAP_UD, exc_invalid_op);
DECLARE_IDTENTRY_RAW(X86_TRAP_BP,		exc_int3);
DECLARE_IDTENTRY_RAW_ERRORCODE(X86_TRAP_PF,	exc_page_fault);

#if defined(CONFIG_IA32_EMULATION)
DECLARE_IDTENTRY_RAW(IA32_SYSCALL_VECTOR,	int80_emulation);
#endif

#ifdef CONFIG_X86_MCE
#ifdef CONFIG_X86_64
DECLARE_IDTENTRY_MCE(X86_TRAP_MC,	exc_machine_check);
Loading