[PATCH] KVM: x86: Inject #UD for AMD-compatible SYSEXIT in long mode

From: Hao Zhang

Date: Tue Oct 20 2026 - 22:12:16 EST


From: Hao Zhang <zhanghao1@xxxxxxxxxx>

AMD specifies that SYSEXIT raises #UD in both 64-bit and compatibility
modes. On AMD hosts, KVM intercepts the hardware #UD, but SYSEXIT is
marked EmulateOnUD and em_sysexit() applies Intel semantics without
checking the guest CPU vendor.

For an AMD-compatible guest at CPL0, this can incorrectly complete
the transition to CPL3 or inject #GP, depending on the SYSENTER_CS
value and other operands. At nonzero CPL, the generic Priv check
injects #GP instead of the required #UD.

Inject #UD for AMD-compatible vCPUs when EFER.LMA is set, before
checking CPL or SYSENTER_CS and before modifying architectural state.

Move the CPL check into em_sysexit() and remove the opcode's Priv
flag so that the generic privilege check does not take precedence
over AMD's long-mode #UD. Retain the existing #GP behavior for real
mode, virtual-8086 mode, and nonzero CPL outside the AMD long-mode
case.

See the SYSEXIT instruction description in AMD APM Volume 3.

Fixes: 4668f0507870 ("KVM: x86 emulator: Add sysexit emulation")
Signed-off-by: Hao Zhang <zhanghao1@xxxxxxxxxx>
---
arch/x86/kvm/emulate.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c
index c1b21282187f..2154a2f2fd92 100644
--- a/arch/x86/kvm/emulate.c
+++ b/arch/x86/kvm/emulate.c
@@ -2457,12 +2457,18 @@ static int em_sysexit(struct x86_emulate_ctxt *ctxt)
const struct x86_emulate_ops *ops = ctxt->ops;
struct desc_struct cs, ss;
u64 msr_data, rcx, rdx;
+ u64 efer = 0;
int usermode;
u16 cs_sel = 0, ss_sel = 0;

- /* inject #GP if in real mode or Virtual 8086 mode */
+ /* AMD rejects SYSEXIT in both IA-32e submodes, even at CPL3. */
+ ops->get_msr(ctxt, MSR_EFER, &efer);
+ if ((efer & EFER_LMA) && !ops->guest_cpuid_is_intel_compatible(ctxt))
+ return emulate_ud(ctxt);
+
+ /* Check CPL here so that AMD's long-mode #UD takes precedence. */
if (ctxt->mode == X86EMUL_MODE_REAL ||
- ctxt->mode == X86EMUL_MODE_VM86)
+ ctxt->mode == X86EMUL_MODE_VM86 || ops->cpl(ctxt))
return emulate_gp(ctxt, 0);

setup_syscalls_segments(&cs, &ss);
@@ -4390,7 +4396,7 @@ static const struct opcode twobyte_table[256] = {
II(ImplicitOps | Priv, em_rdmsr, rdmsr),
IIP(ImplicitOps, em_rdpmc, rdpmc, check_rdpmc),
I(ImplicitOps | EmulateOnUD | IsBranch | ShadowStack, em_sysenter),
- I(ImplicitOps | Priv | EmulateOnUD | IsBranch | ShadowStack, em_sysexit),
+ I(ImplicitOps | EmulateOnUD | IsBranch | ShadowStack, em_sysexit),
N, N,
N, N, N, N, N, N, N, N,
/* 0x40 - 0x4F */

base-commit: 6c377d19d4a5116d9bec5203aa3c6c11523e7898
--
2.15.0