* [PATCH v7 01/20] KVM: x86: Extend VCPU registers for EGPRs
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
@ 2026-09-02 2:17 ` Chang S. Bae
2026-09-02 2:17 ` [PATCH v7 02/20] KVM: VMX: Save guest EGPRs in VCPU cache Chang S. Bae
` (18 subsequent siblings)
19 siblings, 0 replies; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:17 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
Extend the storage to include extended general-purpose registers (EGPRs:
R16-R31) when CONFIG_KVM_APX=y. This option is for VMX only.
Although EGPR state is XSAVE-managed, and thus could be stored in the
guest fpstate, VCPU storage provides a more convenient and efficient
access path for KVM.
Suggested-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
Link: https://lore.kernel.org/abL8SW5JS1aV5goa@google.com
---
arch/x86/include/asm/kvm_host.h | 18 ++++++++++++++++++
arch/x86/kvm/Kconfig | 4 ++++
2 files changed, 22 insertions(+)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 683bb8bf43a9..8a0de8b847ea 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -162,6 +162,24 @@ enum kvm_reg {
VCPU_REGS_R13,
VCPU_REGS_R14,
VCPU_REGS_R15,
+#endif
+#ifdef CONFIG_KVM_APX
+ VCPU_REGS_R16,
+ VCPU_REGS_R17,
+ VCPU_REGS_R18,
+ VCPU_REGS_R19,
+ VCPU_REGS_R20,
+ VCPU_REGS_R21,
+ VCPU_REGS_R22,
+ VCPU_REGS_R23,
+ VCPU_REGS_R24,
+ VCPU_REGS_R25,
+ VCPU_REGS_R26,
+ VCPU_REGS_R27,
+ VCPU_REGS_R28,
+ VCPU_REGS_R29,
+ VCPU_REGS_R30,
+ VCPU_REGS_R31,
#endif
NR_VCPU_GENERAL_PURPOSE_REGS,
diff --git a/arch/x86/kvm/Kconfig b/arch/x86/kvm/Kconfig
index 538ed1e80332..db1427a8b099 100644
--- a/arch/x86/kvm/Kconfig
+++ b/arch/x86/kvm/Kconfig
@@ -93,10 +93,14 @@ config KVM_SW_PROTECTED_VM
If unsure, say "N".
+config KVM_APX
+ bool
+
config KVM_INTEL
tristate "KVM for Intel (and compatible) processors support"
depends on KVM && IA32_FEAT_CTL
select X86_FRED if X86_64
+ select KVM_APX if X86_64
help
Provides support for KVM on processors equipped with Intel's VT
extensions, a.k.a. Virtual Machine Extensions (VMX).
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v7 02/20] KVM: VMX: Save guest EGPRs in VCPU cache
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
2026-09-02 2:17 ` [PATCH v7 01/20] KVM: x86: Extend VCPU registers for EGPRs Chang S. Bae
@ 2026-09-02 2:17 ` Chang S. Bae
2026-09-02 3:26 ` sashiko-bot
2026-09-02 2:17 ` [PATCH v7 03/20] KVM: x86: Support APX state for XSAVE ABI Chang S. Bae
` (17 subsequent siblings)
19 siblings, 1 reply; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:17 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
Save and restore the guest EGPRs on VM exit/entry if the system supports
Advanced Performance Extensions (APX).
There are two possible ways of handling EGPR accesses: reading them from
the processor lazily, or switching them in the vmentry/vmexit path and
accessing them via vcpu->arch.regs. Prepare for the latter; it does not
pollute kvm_reg_read/write() with conditionals to treat GPRs vs. EGPRs
differently, and it allows for future usage of APX registers in the
kernel.
KVM intercepts all XCR0 writes, therefore XCR0 cannot change during the
execution of VMLAUNCH/VMRESUME. Thus, for a guest that does not set
XCR0[APX], the EGPR state does not have to be loaded/saved.
The register saving path is placed after SPEC_CTRL is restored, so as
to avoid any mis-speculation into EGPR accesses when APX is disabled.
While saving may be skipped on VM-Fail, it is not worth the added
complication for a slow path.
Suggested-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
Link: https://lore.kernel.org/adPRA4ZhnvbaXSn0@google.com
---
V6 -> V7: Fix to include update that was slipped into the next patch
---
arch/x86/Kconfig.assembler | 5 +++
arch/x86/include/asm/kvm_vcpu_regs.h | 50 ++++++++++++++++++++++++++++
arch/x86/kvm/Kconfig | 2 +-
arch/x86/kvm/vmenter.h | 1 +
arch/x86/kvm/vmx/vmenter.S | 31 +++++++++++++++--
arch/x86/kvm/vmx/vmx.c | 13 ++++++++
6 files changed, 99 insertions(+), 3 deletions(-)
diff --git a/arch/x86/Kconfig.assembler b/arch/x86/Kconfig.assembler
index b1c59fb0a4c9..1a86efb2a239 100644
--- a/arch/x86/Kconfig.assembler
+++ b/arch/x86/Kconfig.assembler
@@ -1,6 +1,11 @@
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+config AS_APX
+ def_bool $(as-instr64,xor %r16$(comma)%r16)
+ help
+ Supported by binutils >= 2.42 and LLVM integrated assembler >= 18
+
config AS_WRUSS
def_bool $(as-instr64,wrussq %rax$(comma)(%rbx))
help
diff --git a/arch/x86/include/asm/kvm_vcpu_regs.h b/arch/x86/include/asm/kvm_vcpu_regs.h
index d9f1528fab2b..d6658c9909aa 100644
--- a/arch/x86/include/asm/kvm_vcpu_regs.h
+++ b/arch/x86/include/asm/kvm_vcpu_regs.h
@@ -99,6 +99,56 @@
.ifc \r64,%r15
\opd = 15
.endif
+#endif
+#ifdef CONFIG_KVM_APX
+ .ifc \r64,%r16
+ \opd = 16
+ .endif
+ .ifc \r64,%r17
+ \opd = 17
+ .endif
+ .ifc \r64,%r18
+ \opd = 18
+ .endif
+ .ifc \r64,%r19
+ \opd = 19
+ .endif
+ .ifc \r64,%r20
+ \opd = 20
+ .endif
+ .ifc \r64,%r21
+ \opd = 21
+ .endif
+ .ifc \r64,%r22
+ \opd = 22
+ .endif
+ .ifc \r64,%r23
+ \opd = 23
+ .endif
+ .ifc \r64,%r24
+ \opd = 24
+ .endif
+ .ifc \r64,%r25
+ \opd = 25
+ .endif
+ .ifc \r64,%r26
+ \opd = 26
+ .endif
+ .ifc \r64,%r27
+ \opd = 27
+ .endif
+ .ifc \r64,%r28
+ \opd = 28
+ .endif
+ .ifc \r64,%r29
+ \opd = 29
+ .endif
+ .ifc \r64,%r30
+ \opd = 30
+ .endif
+ .ifc \r64,%r31
+ \opd = 31
+ .endif
#endif
.endm
diff --git a/arch/x86/kvm/Kconfig b/arch/x86/kvm/Kconfig
index db1427a8b099..e55462068bed 100644
--- a/arch/x86/kvm/Kconfig
+++ b/arch/x86/kvm/Kconfig
@@ -100,7 +100,7 @@ config KVM_INTEL
tristate "KVM for Intel (and compatible) processors support"
depends on KVM && IA32_FEAT_CTL
select X86_FRED if X86_64
- select KVM_APX if X86_64
+ select KVM_APX if X86_64 && AS_APX
help
Provides support for KVM on processors equipped with Intel's VT
extensions, a.k.a. Virtual Machine Extensions (VMX).
diff --git a/arch/x86/kvm/vmenter.h b/arch/x86/kvm/vmenter.h
index 77376812c81b..690c05696824 100644
--- a/arch/x86/kvm/vmenter.h
+++ b/arch/x86/kvm/vmenter.h
@@ -7,6 +7,7 @@
#define KVM_ENTER_VMRESUME BIT(0)
#define KVM_ENTER_SAVE_SPEC_CTRL BIT(1)
#define KVM_ENTER_CLEAR_CPU_BUFFERS_FOR_MMIO BIT(2)
+#define KVM_ENTER_EGPR_SWITCH BIT(3)
#ifdef __ASSEMBLER__
.macro RESTORE_GUEST_SPEC_CTRL_BODY guest_spec_ctrl:req, label:req
diff --git a/arch/x86/kvm/vmx/vmenter.S b/arch/x86/kvm/vmx/vmenter.S
index 00d807505fc8..02308fb36f0f 100644
--- a/arch/x86/kvm/vmx/vmenter.S
+++ b/arch/x86/kvm/vmx/vmenter.S
@@ -16,6 +16,7 @@
* @flags: KVM_ENTER_VMRESUME: use VMRESUME instead of VMLAUNCH
* KVM_ENTER_SAVE_SPEC_CTRL: save guest SPEC_CTRL into vmx->spec_ctrl
* KVM_ENTER_CLEAR_CPU_BUFFERS_FOR_MMIO: vCPU can access host MMIO
+ * KVM_ENTER_EGPR_SWITCH: load/store guest EGPRs
*
* Returns:
* 0 on VM-Exit, 1 on VM-Fail
@@ -46,6 +47,16 @@ SYM_FUNC_START(__vmx_vcpu_run)
/* Reload @vmx, _ASM_ARG1 may be modified by vmx_update_host_rsp(). */
mov WORD_SIZE(%_ASM_SP), %_ASM_DI
+#ifdef CONFIG_KVM_APX
+ ALTERNATIVE "jmp .Lload_egprs_done", "", X86_FEATURE_APX
+ testl $KVM_ENTER_EGPR_SWITCH, (%_ASM_SP)
+ jz .Lload_egprs_done
+ LOAD_REGS %_ASM_DI, VMX_vcpu_arch_regs, \
+ %r16, %r17, %r18, %r19, %r20, %r21, %r22, %r23, \
+ %r24, %r25, %r26, %r27, %r28, %r29, %r30, %r31
+.Lload_egprs_done:
+#endif
+
/*
* Unlike AMD there's no V_SPEC_CTRL here, so do not leave the body
* out of line. Clobbers RAX, RCX, RDX, RSI.
@@ -193,8 +204,24 @@ SYM_INNER_LABEL_ALIGN(vmx_vmexit, SYM_L_GLOBAL)
mov %_ASM_BX, %_ASM_AX
/* Pop our saved arguments from the stack */
- pop %_ASM_BX
- pop %_ASM_BX
+ pop %_ASM_BX /* @flags */
+ pop %_ASM_DI /* @vmx */
+
+#ifdef CONFIG_KVM_APX
+ ALTERNATIVE "jmp .Lclear_egprs_done", "", X86_FEATURE_APX
+ test $KVM_ENTER_EGPR_SWITCH, %_ASM_BX
+ jz .Lclear_egprs_done
+ /*
+ * Unlike legacy GPRs, saving could be conditional here on VM-Fail,
+ * which however isn't in fastpath. Instead, always save EGPRs.
+ */
+ STORE_REGS %_ASM_DI, VMX_vcpu_arch_regs, \
+ %r16, %r17, %r18, %r19, %r20, %r21, %r22, %r23, \
+ %r24, %r25, %r26, %r27, %r28, %r29, %r30, %r31
+ CLEAR_REGS %r16d, %r17d, %r18d, %r19d, %r20d, %r21d, %r22d, %r23d, \
+ %r24d, %r25d, %r26d, %r27d, %r28d, %r29d, %r30d, %r31d
+.Lclear_egprs_done:
+#endif
/* ... and then the callee-save registers */
pop %_ASM_BX
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 612ab07d4100..cea5c273f149 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -994,6 +994,19 @@ unsigned int __vmx_vcpu_enter_flags(struct vcpu_vmx *vmx)
kvm_vcpu_can_access_host_mmio(&vmx->vcpu))
flags |= KVM_ENTER_CLEAR_CPU_BUFFERS_FOR_MMIO;
+ /*
+ * KVM intercepts XSETBV and thus always tracks the guest XCR0. EGPR
+ * save/restore is gated by this flag. The resulting behavior is:
+ *
+ * - When the guest enables APX, KVM restores EGPRs (initially zeroed).
+ * - When the guest disables APX, EGPRs are preserved in the VCPU cache.
+ * - When APX is re-enabled, the saved state is restored, which matches
+ * architectural expectations.
+ */
+ if (IS_ENABLED(CONFIG_KVM_APX) && cpu_feature_enabled(X86_FEATURE_APX) &&
+ vmx->vcpu.arch.xcr0 & XFEATURE_MASK_APX)
+ flags |= KVM_ENTER_EGPR_SWITCH;
+
return flags;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v7 02/20] KVM: VMX: Save guest EGPRs in VCPU cache
2026-09-02 2:17 ` [PATCH v7 02/20] KVM: VMX: Save guest EGPRs in VCPU cache Chang S. Bae
@ 2026-09-02 3:26 ` sashiko-bot
0 siblings, 0 replies; 29+ messages in thread
From: sashiko-bot @ 2026-09-02 3:26 UTC (permalink / raw)
To: Chang S. Bae; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Host kernel data in Extended General Purpose Registers (EGPRs) is leaked to a malicious guest via speculative execution when the guest operates with APX disabled.
--
commit 70442dd137e1b61dbdd5b674afc21cfb7948b2b6
Author: Chang S. Bae <chang.seok.bae@intel.com>
KVM: VMX: Save guest EGPRs in VCPU cache
This commit saves and restores the guest EGPRs on VM exit/entry if the
system supports Advanced Performance Extensions (APX). It skips loading the
EGPR state for guests that do not set XCR0[APX], and places the register
saving path after SPEC_CTRL is restored.
Link: https://lore.kernel.org/adPRA4ZhnvbaXSn0@google.com
> diff --git a/arch/x86/kvm/vmx/vmenter.S b/arch/x86/kvm/vmx/vmenter.S
> index 00d807505fc88..02308fb36f0f5 100644
> --- a/arch/x86/kvm/vmx/vmenter.S
> +++ b/arch/x86/kvm/vmx/vmenter.S
[ ... ]
> @@ -46,6 +47,16 @@ SYM_FUNC_START(__vmx_vcpu_run)
> /* Reload @vmx, _ASM_ARG1 may be modified by vmx_update_host_rsp(). */
> mov WORD_SIZE(%_ASM_SP), %_ASM_DI
>
> +#ifdef CONFIG_KVM_APX
> + ALTERNATIVE "jmp .Lload_egprs_done", "", X86_FEATURE_APX
> + testl $KVM_ENTER_EGPR_SWITCH, (%_ASM_SP)
> + jz .Lload_egprs_done
> + LOAD_REGS %_ASM_DI, VMX_vcpu_arch_regs, \
> + %r16, %r17, %r18, %r19, %r20, %r21, %r22, %r23, \
> + %r24, %r25, %r26, %r27, %r28, %r29, %r30, %r31
> +.Lload_egprs_done:
> +#endif
[Severity: High]
In __vmx_vcpu_run(), when a guest runs with APX disabled,
KVM_ENTER_EGPR_SWITCH is clear and this branch skips loading the guest's
EGPRs. Are the host's physical %r16 through %r31 register values left
intact upon entering the guest when this happens?
If so, could a malicious guest with APX disabled speculatively execute APX
instructions before the architectural #UD exception retires, allowing it to
read the host's %r16 through %r31 values and leak them via a cache
side-channel?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260902021804.718676-1-chang.seok.bae@intel.com?part=2
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v7 03/20] KVM: x86: Support APX state for XSAVE ABI
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
2026-09-02 2:17 ` [PATCH v7 01/20] KVM: x86: Extend VCPU registers for EGPRs Chang S. Bae
2026-09-02 2:17 ` [PATCH v7 02/20] KVM: VMX: Save guest EGPRs in VCPU cache Chang S. Bae
@ 2026-09-02 2:17 ` Chang S. Bae
2026-09-02 3:08 ` sashiko-bot
2026-09-02 2:17 ` [PATCH v7 04/20] KVM: VMX: Refactor VMX instruction information access Chang S. Bae
` (16 subsequent siblings)
19 siblings, 1 reply; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:17 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
Introduce a facility to copy APX state between the VCPU cache and the
userspace buffer as the VCPU cache is the single source of truth.
The existing fpstate copy functions historically sync all XSTATEs in
between userspace and kernel buffers [1]. In this regard, any additional
state handling logic should be consistent with them -- i.e. validation of
XSTATE_BV against the supported XCR0 mask.
Now with the two copy paths, their invocations require to take care of
these facts:
* When exporting to userspace, the fpstate function should run first
since it zeros out the area of components either not present or
inactive. Then the VCPU cache function ensures copying APX state.
* On the opposite way, both will copy the state to both storages. This
duplication is avoidable by tweaking either the generic function or
XSTATE_BV/XCR0. But the optimization while at a slow path looks to
rather add complication. So keep the implementation simple.
[1] Except for PKRU state, as stored in struct thread_struct.
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
arch/x86/kvm/cpuid.c | 10 ++++++++
arch/x86/kvm/cpuid.h | 2 ++
arch/x86/kvm/x86.c | 61 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 73 insertions(+)
diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
index 851f151efb35..a3f583a376ab 100644
--- a/arch/x86/kvm/cpuid.c
+++ b/arch/x86/kvm/cpuid.c
@@ -60,6 +60,16 @@ void __init kvm_init_xstate_sizes(void)
}
}
+u32 xstate_size(unsigned int xfeature)
+{
+ return xstate_sizes[xfeature].eax;
+}
+
+u32 xstate_offset(unsigned int xfeature)
+{
+ return xstate_sizes[xfeature].ebx;
+}
+
u32 xstate_required_size(u64 xstate_bv, bool compacted)
{
u32 ret = XSAVE_HDR_SIZE + XSAVE_HDR_OFFSET;
diff --git a/arch/x86/kvm/cpuid.h b/arch/x86/kvm/cpuid.h
index 8d863f45585d..1716f3b6b885 100644
--- a/arch/x86/kvm/cpuid.h
+++ b/arch/x86/kvm/cpuid.h
@@ -66,6 +66,8 @@ bool kvm_cpuid(struct kvm_vcpu *vcpu, u32 *eax, u32 *ebx,
void __init kvm_init_xstate_sizes(void);
u32 xstate_required_size(u64 xstate_bv, bool compacted);
+u32 xstate_size(unsigned int xfeature);
+u32 xstate_offset(unsigned int xfeature);
int cpuid_query_maxphyaddr(struct kvm_vcpu *vcpu);
int cpuid_query_maxguestphyaddr(struct kvm_vcpu *vcpu);
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 79468ddfe473..0df74422759f 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -3141,6 +3141,49 @@ static int kvm_vcpu_ioctl_x86_set_vcpu_events(struct kvm_vcpu *vcpu,
return 0;
}
+#ifdef CONFIG_KVM_APX
+static void kvm_copy_vcpu_regs_to_uabi(struct kvm_vcpu *vcpu, void *buf, u64 supported_xcr0)
+{
+ union fpregs_state *xstate = (union fpregs_state *)buf;
+
+ BUILD_BUG_ON(NR_VCPU_GENERAL_PURPOSE_REGS <= VCPU_REGS_R31);
+
+ if (!(supported_xcr0 & XFEATURE_MASK_APX))
+ return;
+
+ memcpy(buf + xstate_offset(XFEATURE_APX),
+ &vcpu->arch.regs[VCPU_REGS_R16],
+ xstate_size(XFEATURE_APX));
+
+ xstate->xsave.header.xfeatures |= XFEATURE_MASK_APX;
+}
+
+static int kvm_copy_uabi_to_vcpu_regs(struct kvm_vcpu *vcpu, void *buf, u64 supported_xcr0)
+{
+ union fpregs_state *xstate = (union fpregs_state *)buf;
+
+ if (!(xstate->xsave.header.xfeatures & XFEATURE_MASK_APX))
+ return 0;
+
+ if (!(supported_xcr0 & XFEATURE_MASK_APX))
+ return -EINVAL;
+
+ BUILD_BUG_ON(NR_VCPU_GENERAL_PURPOSE_REGS <= VCPU_REGS_R31);
+
+ memcpy(&vcpu->arch.regs[VCPU_REGS_R16],
+ buf + xstate_offset(XFEATURE_APX),
+ xstate_size(XFEATURE_APX));
+
+ return 0;
+}
+#else
+static void kvm_copy_vcpu_regs_to_uabi(struct kvm_vcpu *vcpu, void *buf, u64 supported_xcr0) { }
+static int kvm_copy_uabi_to_vcpu_regs(struct kvm_vcpu *vcpu, void *buf, u64 supported_xcr0)
+{
+ return 0;
+}
+#endif
+
static int kvm_vcpu_ioctl_x86_get_xsave2(struct kvm_vcpu *vcpu,
u8 *state, unsigned int size)
{
@@ -3162,8 +3205,15 @@ static int kvm_vcpu_ioctl_x86_get_xsave2(struct kvm_vcpu *vcpu,
if (fpstate_is_confidential(&vcpu->arch.guest_fpu))
return vcpu->kvm->arch.has_protected_state ? -EINVAL : 0;
+ /*
+ * This copy function zeros out userspace memory for any gap from the
+ * guest fpstate. So invoke before copying any other state, i.e. APX,
+ * that is not saved in fpstate.
+ */
fpu_copy_guest_fpstate_to_uabi(&vcpu->arch.guest_fpu, state, size,
supported_xcr0, vcpu->arch.pkru);
+ kvm_copy_vcpu_regs_to_uabi(vcpu, state, supported_xcr0);
+
return 0;
}
@@ -3178,6 +3228,7 @@ static int kvm_vcpu_ioctl_x86_set_xsave(struct kvm_vcpu *vcpu,
struct kvm_xsave *guest_xsave)
{
union fpregs_state *xstate = (union fpregs_state *)guest_xsave->region;
+ int err;
if (fpstate_is_confidential(&vcpu->arch.guest_fpu))
return vcpu->kvm->arch.has_protected_state ? -EINVAL : 0;
@@ -3189,6 +3240,16 @@ static int kvm_vcpu_ioctl_x86_set_xsave(struct kvm_vcpu *vcpu,
*/
xstate->xsave.header.xfeatures &= ~vcpu->arch.guest_fpu.fpstate->xfd;
+ /*
+ * This and the following copy functions copy APX state into each
+ * storage. The VCPU cache is the single source of truth but avoiding
+ * this redundant copy tends to introduce special-case handling. Since
+ * this lies in a slow path, keep the implementation simple.
+ */
+ err = kvm_copy_uabi_to_vcpu_regs(vcpu, guest_xsave->region, kvm_caps.supported_xcr0);
+ if (err)
+ return err;
+
return fpu_copy_uabi_to_guest_fpstate(&vcpu->arch.guest_fpu,
guest_xsave->region,
kvm_caps.supported_xcr0,
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v7 03/20] KVM: x86: Support APX state for XSAVE ABI
2026-09-02 2:17 ` [PATCH v7 03/20] KVM: x86: Support APX state for XSAVE ABI Chang S. Bae
@ 2026-09-02 3:08 ` sashiko-bot
0 siblings, 0 replies; 29+ messages in thread
From: sashiko-bot @ 2026-09-02 3:08 UTC (permalink / raw)
To: Chang S. Bae; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] KVM fails to zero out APX registers (R16-R31) when restoring an XSAVE buffer where the APX feature bit is cleared, leaking stale state across snapshot restorations.
Pre-existing issues:
- [Critical] Enabling APX support inherently inflates the default FPU uABI size beyond 4096 bytes, unconditionally breaking the legacy KVM_GET_XSAVE ioctl on APX-capable hosts.
--
commit e523aa83189e420a1aea25a00998b66b7d7b10d8
Author: Chang S. Bae <chang.seok.bae@intel.com>
KVM: x86: Support APX state for XSAVE ABI
This commit introduces a facility to copy APX state between the VCPU cache
and the userspace buffer for the XSAVE ABI. It adds two copy functions that
run during state export and import to ensure APX state is correctly handled
since the VCPU cache acts as the single source of truth.
> Introduce a facility to copy APX state between the VCPU cache and the
> userspace buffer as the VCPU cache is the single source of truth.
[Severity: Critical]
This is a pre-existing issue, but does enabling APX support inherently
inflate the default FPU uABI size beyond 4096 bytes, unconditionally
breaking the legacy KVM_GET_XSAVE ioctl on APX-capable hosts?
In arch/x86/kvm/x86.c:kvm_arch_vcpu_ioctl(), the legacy KVM_GET_XSAVE
ioctl unconditionally rejects the call if the size is too large:
case KVM_GET_XSAVE: {
r = -EINVAL;
if (vcpu->arch.guest_fpu.uabi_size > sizeof(struct kvm_xsave))
break;
Because APX statically inflates the host's default_size beyond 4096 bytes
(the size of struct kvm_xsave), and the core FPU subsystem initializes
vcpu->arch.guest_fpu.uabi_size to this host default size, the legacy ioctl
will instantly fail with -EINVAL. Can this regression prevent VM boot,
migration, or snapshotting for legacy userspace (e.g., older versions of
QEMU)?
> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index 79468ddfe4736..0df74422759f8 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
[ ... ]
> @@ -3141,6 +3141,49 @@ static int kvm_vcpu_ioctl_x86_set_vcpu_events(struct kvm_vcpu *vcpu,
> return 0;
> }
>
> +#ifdef CONFIG_KVM_APX
[ ... ]
> +static int kvm_copy_uabi_to_vcpu_regs(struct kvm_vcpu *vcpu, void *buf, u64 supported_xcr0)
> +{
> + union fpregs_state *xstate = (union fpregs_state *)buf;
> +
> + if (!(xstate->xsave.header.xfeatures & XFEATURE_MASK_APX))
> + return 0;
[Severity: High]
When restoring an XSAVE buffer where the XFEATURE_MASK_APX bit is cleared
in the header, does this code fail to zero out the APX registers (R16-R31)
in the software vCPU cache?
Since the vCPU cache is the single source of truth, returning immediately
leaves stale non-zero values from a previous vCPU execution. Will these
stale values be incorrectly loaded into hardware during VM-entry, violating
the x86 architecture requirement that missing XSAVE features are restored to
their INIT (zero) state?
> +
> + if (!(supported_xcr0 & XFEATURE_MASK_APX))
> + return -EINVAL;
> +
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260902021804.718676-1-chang.seok.bae@intel.com?part=3
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v7 04/20] KVM: VMX: Refactor VMX instruction information access
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (2 preceding siblings ...)
2026-09-02 2:17 ` [PATCH v7 03/20] KVM: x86: Support APX state for XSAVE ABI Chang S. Bae
@ 2026-09-02 2:17 ` Chang S. Bae
2026-09-02 2:17 ` [PATCH v7 05/20] KVM: VMX: Refactor instruction information decoding Chang S. Bae
` (15 subsequent siblings)
19 siblings, 0 replies; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:17 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
Introduce a helper that returns the instruction information as a 64-bit
value and adjust existing sites to prepare for a wider field.
The VMX instruction information field is currently 32 bits. Future
extensions may expand this field to support extended register IDs,
requiring a wider width. The change provides a single access point for
the transition.
No functional change intended.
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
arch/x86/kvm/vmx/nested.c | 23 ++++++++++++-----------
arch/x86/kvm/vmx/nested.h | 2 +-
arch/x86/kvm/vmx/vmx.c | 6 +++---
arch/x86/kvm/vmx/vmx.h | 10 ++++++++--
4 files changed, 24 insertions(+), 17 deletions(-)
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 151873407abd..67168cd3e7af 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -5242,7 +5242,7 @@ static void nested_vmx_triple_fault(struct kvm_vcpu *vcpu)
* #UD, #GP, or #SS.
*/
int get_vmx_mem_address(struct kvm_vcpu *vcpu, unsigned long exit_qualification,
- u32 vmx_instruction_info, bool wr, int len, gva_t *ret)
+ u64 vmx_instruction_info, bool wr, int len, gva_t *ret)
{
gva_t off;
bool exn;
@@ -5374,7 +5374,7 @@ static int nested_vmx_get_vmptr(struct kvm_vcpu *vcpu, gpa_t *vmpointer,
int r;
if (get_vmx_mem_address(vcpu, vmx_get_exit_qual(vcpu),
- vmcs_read32(VMX_INSTRUCTION_INFO), false,
+ vmx_get_instr_info(), false,
sizeof(*vmpointer), &gva)) {
*ret = 1;
return -EINVAL;
@@ -5666,7 +5666,7 @@ static int handle_vmread(struct kvm_vcpu *vcpu)
struct vmcs12 *vmcs12 = is_guest_mode(vcpu) ? get_shadow_vmcs12(vcpu)
: get_vmcs12(vcpu);
unsigned long exit_qualification = vmx_get_exit_qual(vcpu);
- u32 instr_info = vmcs_read32(VMX_INSTRUCTION_INFO);
+ u64 instr_info = vmx_get_instr_info();
struct vcpu_vmx *vmx = to_vmx(vcpu);
struct x86_exception e;
unsigned long field;
@@ -5772,7 +5772,7 @@ static int handle_vmwrite(struct kvm_vcpu *vcpu)
struct vmcs12 *vmcs12 = is_guest_mode(vcpu) ? get_shadow_vmcs12(vcpu)
: get_vmcs12(vcpu);
unsigned long exit_qualification = vmx_get_exit_qual(vcpu);
- u32 instr_info = vmcs_read32(VMX_INSTRUCTION_INFO);
+ u64 instr_info = vmx_get_instr_info();
struct vcpu_vmx *vmx = to_vmx(vcpu);
struct x86_exception e;
unsigned long field;
@@ -5961,7 +5961,7 @@ static int handle_vmptrld(struct kvm_vcpu *vcpu)
static int handle_vmptrst(struct kvm_vcpu *vcpu)
{
unsigned long exit_qual = vmx_get_exit_qual(vcpu);
- u32 instr_info = vmcs_read32(VMX_INSTRUCTION_INFO);
+ u64 instr_info = vmx_get_instr_info();
gpa_t current_vmptr = to_vmx(vcpu)->nested.current_vmptr;
struct x86_exception e;
gva_t gva;
@@ -5989,8 +5989,8 @@ static int handle_vmptrst(struct kvm_vcpu *vcpu)
static int handle_invept(struct kvm_vcpu *vcpu)
{
struct vcpu_vmx *vmx = to_vmx(vcpu);
- u32 vmx_instruction_info, types;
unsigned long type, roots_to_free;
+ u64 vmx_instruction_info;
struct kvm_mmu *mmu;
gva_t gva;
struct x86_exception e;
@@ -5998,6 +5998,7 @@ static int handle_invept(struct kvm_vcpu *vcpu)
u64 eptp, gpa;
} operand;
int i, r, gpr_index;
+ u32 types;
if (!(vmx->nested.msrs.secondary_ctls_high &
SECONDARY_EXEC_ENABLE_EPT) ||
@@ -6009,7 +6010,7 @@ static int handle_invept(struct kvm_vcpu *vcpu)
if (!nested_vmx_check_permission(vcpu))
return 1;
- vmx_instruction_info = vmcs_read32(VMX_INSTRUCTION_INFO);
+ vmx_instruction_info = vmx_get_instr_info();
gpr_index = vmx_get_instr_info_reg2(vmx_instruction_info);
type = kvm_register_read(vcpu, gpr_index);
@@ -6069,7 +6070,7 @@ static int handle_invept(struct kvm_vcpu *vcpu)
static int handle_invvpid(struct kvm_vcpu *vcpu)
{
struct vcpu_vmx *vmx = to_vmx(vcpu);
- u32 vmx_instruction_info;
+ u64 vmx_instruction_info;
unsigned long type, types;
gva_t gva;
struct x86_exception e;
@@ -6090,7 +6091,7 @@ static int handle_invvpid(struct kvm_vcpu *vcpu)
if (!nested_vmx_check_permission(vcpu))
return 1;
- vmx_instruction_info = vmcs_read32(VMX_INSTRUCTION_INFO);
+ vmx_instruction_info = vmx_get_instr_info();
gpr_index = vmx_get_instr_info_reg2(vmx_instruction_info);
type = kvm_register_read(vcpu, gpr_index);
@@ -6435,7 +6436,7 @@ static bool nested_vmx_exit_handled_encls(struct kvm_vcpu *vcpu,
static bool nested_vmx_exit_handled_vmcs_access(struct kvm_vcpu *vcpu,
struct vmcs12 *vmcs12, gpa_t bitmap)
{
- u32 vmx_instruction_info;
+ u64 vmx_instruction_info;
unsigned long field;
u8 b;
@@ -6443,7 +6444,7 @@ static bool nested_vmx_exit_handled_vmcs_access(struct kvm_vcpu *vcpu,
return true;
/* Decode instruction info and find the field to access */
- vmx_instruction_info = vmcs_read32(VMX_INSTRUCTION_INFO);
+ vmx_instruction_info = vmx_get_instr_info();
field = kvm_register_read(vcpu, (((vmx_instruction_info) >> 28) & 0xf));
/* Out-of-range fields always cause a VM exit from L2 to L1 */
diff --git a/arch/x86/kvm/vmx/nested.h b/arch/x86/kvm/vmx/nested.h
index c6de848bd9ce..6bbccaa8263a 100644
--- a/arch/x86/kvm/vmx/nested.h
+++ b/arch/x86/kvm/vmx/nested.h
@@ -51,7 +51,7 @@ void nested_sync_vmcs12_to_shadow(struct kvm_vcpu *vcpu);
int vmx_set_vmx_msr(struct kvm_vcpu *vcpu, u32 msr_index, u64 data);
int vmx_get_vmx_msr(struct nested_vmx_msrs *msrs, u32 msr_index, u64 *pdata);
int get_vmx_mem_address(struct kvm_vcpu *vcpu, unsigned long exit_qualification,
- u32 vmx_instruction_info, bool wr, int len, gva_t *ret);
+ u64 vmx_instruction_info, bool wr, int len, gva_t *ret);
bool nested_vmx_check_io_bitmaps(struct kvm_vcpu *vcpu, unsigned int port,
int size);
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index cea5c273f149..74c4b9dbe29b 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -6154,7 +6154,7 @@ static int handle_monitor_trap(struct kvm_vcpu *vcpu)
static int handle_invpcid(struct kvm_vcpu *vcpu)
{
- u32 vmx_instruction_info;
+ u64 vmx_instruction_info;
unsigned long type;
gva_t gva;
struct {
@@ -6168,7 +6168,7 @@ static int handle_invpcid(struct kvm_vcpu *vcpu)
return 1;
}
- vmx_instruction_info = vmcs_read32(VMX_INSTRUCTION_INFO);
+ vmx_instruction_info = vmx_get_instr_info();
gpr_index = vmx_get_instr_info_reg2(vmx_instruction_info);
type = kvm_register_read(vcpu, gpr_index);
@@ -6319,7 +6319,7 @@ static int handle_notify(struct kvm_vcpu *vcpu)
static int vmx_get_msr_imm_reg(struct kvm_vcpu *vcpu)
{
- return vmx_get_instr_info_reg(vmcs_read32(VMX_INSTRUCTION_INFO));
+ return vmx_get_instr_info_reg(vmx_get_instr_info());
}
static int handle_rdmsr_imm(struct kvm_vcpu *vcpu)
diff --git a/arch/x86/kvm/vmx/vmx.h b/arch/x86/kvm/vmx/vmx.h
index dc8517f15bc4..4751f27f006b 100644
--- a/arch/x86/kvm/vmx/vmx.h
+++ b/arch/x86/kvm/vmx/vmx.h
@@ -709,12 +709,18 @@ static inline bool vmx_guest_state_valid(struct kvm_vcpu *vcpu)
void dump_vmcs(struct kvm_vcpu *vcpu);
-static inline int vmx_get_instr_info_reg(u32 vmx_instr_info)
+/* A placeholder to smoothen 64-bit extension */
+static inline u64 vmx_get_instr_info(void)
+{
+ return vmcs_read32(VMX_INSTRUCTION_INFO);
+}
+
+static inline int vmx_get_instr_info_reg(u64 vmx_instr_info)
{
return (vmx_instr_info >> 3) & 0xf;
}
-static inline int vmx_get_instr_info_reg2(u32 vmx_instr_info)
+static inline int vmx_get_instr_info_reg2(u64 vmx_instr_info)
{
return (vmx_instr_info >> 28) & 0xf;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v7 05/20] KVM: VMX: Refactor instruction information decoding
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (3 preceding siblings ...)
2026-09-02 2:17 ` [PATCH v7 04/20] KVM: VMX: Refactor VMX instruction information access Chang S. Bae
@ 2026-09-02 2:17 ` Chang S. Bae
2026-09-02 2:17 ` [PATCH v7 06/20] KVM: VMX: Remove unused control-register access defines Chang S. Bae
` (14 subsequent siblings)
19 siblings, 0 replies; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:17 UTC (permalink / raw)
To: pbonzini, seanjc
Cc: kvm, x86, linux-kernel, chang.seok.bae, kernel test robot
KVM currently decodes the VMX instruction information field using a mix
of open-coded bit manipulations and ad hoc helpers. Convert all decoding
to use helpers to centralize the decoding logic for the transition to a
wider instruction information.
No functional change intended.
Tested-by: kernel test robot <oliver.sang@intel.com>
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
arch/x86/kvm/vmx/nested.c | 58 +++++++++++++++++++--------------------
arch/x86/kvm/vmx/vmx.c | 9 +++---
arch/x86/kvm/vmx/vmx.h | 48 +++++++++++++++++++++++++++++---
3 files changed, 77 insertions(+), 38 deletions(-)
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 67168cd3e7af..f05ac3c9aa12 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -5242,7 +5242,7 @@ static void nested_vmx_triple_fault(struct kvm_vcpu *vcpu)
* #UD, #GP, or #SS.
*/
int get_vmx_mem_address(struct kvm_vcpu *vcpu, unsigned long exit_qualification,
- u64 vmx_instruction_info, bool wr, int len, gva_t *ret)
+ u64 instr_info, bool wr, int len, gva_t *ret)
{
gva_t off;
bool exn;
@@ -5250,20 +5250,20 @@ int get_vmx_mem_address(struct kvm_vcpu *vcpu, unsigned long exit_qualification,
/*
* According to Vol. 3B, "Information for VM Exits Due to Instruction
- * Execution", on an exit, vmx_instruction_info holds most of the
- * addressing components of the operand. Only the displacement part
- * is put in exit_qualification (see 3B, "Basic VM-Exit Information").
+ * Execution", on an exit, instr_info holds most of the addressing
+ * components of the operand. Only the displacement part is put in
+ * exit_qualification (see 3B, "Basic VM-Exit Information").
* For how an actual address is calculated from all these components,
* refer to Vol. 1, "Operand Addressing".
*/
- int scaling = vmx_instruction_info & 3;
- int addr_size = (vmx_instruction_info >> 7) & 7;
- bool is_reg = vmx_instruction_info & (1u << 10);
- int seg_reg = (vmx_instruction_info >> 15) & 7;
- int index_reg = (vmx_instruction_info >> 18) & 0xf;
- bool index_is_valid = !(vmx_instruction_info & (1u << 22));
- int base_reg = (vmx_instruction_info >> 23) & 0xf;
- bool base_is_valid = !(vmx_instruction_info & (1u << 27));
+ int scaling = vmx_get_instr_info_scaling(instr_info);
+ int addr_size = vmx_get_instr_info_addr_size(instr_info);
+ bool is_reg = vmx_get_instr_info_is_reg(instr_info);
+ int seg_reg = vmx_get_instr_info_seg_reg(instr_info);
+ int index_reg = vmx_get_instr_info_index_reg(instr_info);
+ bool index_is_valid = vmx_get_instr_info_index_is_valid(instr_info);
+ int base_reg = vmx_get_instr_info_base_reg(instr_info);
+ bool base_is_valid = vmx_get_instr_info_base_is_valid(instr_info);
if (is_reg) {
kvm_queue_exception(vcpu, UD_VECTOR);
@@ -5679,7 +5679,7 @@ static int handle_vmread(struct kvm_vcpu *vcpu)
return 1;
/* Decode instruction info and find the field to read */
- field = kvm_register_read(vcpu, (((instr_info) >> 28) & 0xf));
+ field = kvm_register_read(vcpu, vmx_get_instr_info_reg2(instr_info));
if (!nested_vmx_is_evmptr12_valid(vmx)) {
/*
@@ -5727,8 +5727,8 @@ static int handle_vmread(struct kvm_vcpu *vcpu)
* Note that the number of bits actually copied is 32 or 64 depending
* on the guest's mode (32 or 64 bit), not on the given field's length.
*/
- if (instr_info & BIT(10)) {
- kvm_register_write(vcpu, (((instr_info) >> 3) & 0xf), value);
+ if (vmx_get_instr_info_is_reg(instr_info)) {
+ kvm_register_write(vcpu, vmx_get_instr_info_reg(instr_info), value);
} else {
len = is_64_bit_mode(vcpu) ? 8 : 4;
if (get_vmx_mem_address(vcpu, exit_qualification,
@@ -5801,8 +5801,8 @@ static int handle_vmwrite(struct kvm_vcpu *vcpu)
get_vmcs12(vcpu)->vmcs_link_pointer == INVALID_GPA))
return nested_vmx_failInvalid(vcpu);
- if (instr_info & BIT(10))
- value = kvm_register_read(vcpu, (((instr_info) >> 3) & 0xf));
+ if (vmx_get_instr_info_is_reg(instr_info))
+ value = kvm_register_read(vcpu, vmx_get_instr_info_reg(instr_info));
else {
len = is_64_bit_mode(vcpu) ? 8 : 4;
if (get_vmx_mem_address(vcpu, exit_qualification,
@@ -5813,7 +5813,7 @@ static int handle_vmwrite(struct kvm_vcpu *vcpu)
return kvm_handle_memory_failure(vcpu, r, &e);
}
- field = kvm_register_read(vcpu, (((instr_info) >> 28) & 0xf));
+ field = kvm_register_read(vcpu, vmx_get_instr_info_reg2(instr_info));
offset = get_vmcs12_field_offset(field);
if (offset < 0)
@@ -5990,7 +5990,7 @@ static int handle_invept(struct kvm_vcpu *vcpu)
{
struct vcpu_vmx *vmx = to_vmx(vcpu);
unsigned long type, roots_to_free;
- u64 vmx_instruction_info;
+ u64 instr_info;
struct kvm_mmu *mmu;
gva_t gva;
struct x86_exception e;
@@ -6010,8 +6010,8 @@ static int handle_invept(struct kvm_vcpu *vcpu)
if (!nested_vmx_check_permission(vcpu))
return 1;
- vmx_instruction_info = vmx_get_instr_info();
- gpr_index = vmx_get_instr_info_reg2(vmx_instruction_info);
+ instr_info = vmx_get_instr_info();
+ gpr_index = vmx_get_instr_info_reg2(instr_info);
type = kvm_register_read(vcpu, gpr_index);
types = (vmx->nested.msrs.ept_caps >> VMX_EPT_EXTENT_SHIFT) & 6;
@@ -6023,7 +6023,7 @@ static int handle_invept(struct kvm_vcpu *vcpu)
* operand is read even if it isn't needed (e.g., for type==global)
*/
if (get_vmx_mem_address(vcpu, vmx_get_exit_qual(vcpu),
- vmx_instruction_info, false, sizeof(operand), &gva))
+ instr_info, false, sizeof(operand), &gva))
return 1;
r = kvm_read_guest_virt(vcpu, gva, &operand, sizeof(operand), &e);
if (r != X86EMUL_CONTINUE)
@@ -6070,8 +6070,8 @@ static int handle_invept(struct kvm_vcpu *vcpu)
static int handle_invvpid(struct kvm_vcpu *vcpu)
{
struct vcpu_vmx *vmx = to_vmx(vcpu);
- u64 vmx_instruction_info;
unsigned long type, types;
+ u64 instr_info;
gva_t gva;
struct x86_exception e;
struct {
@@ -6091,8 +6091,8 @@ static int handle_invvpid(struct kvm_vcpu *vcpu)
if (!nested_vmx_check_permission(vcpu))
return 1;
- vmx_instruction_info = vmx_get_instr_info();
- gpr_index = vmx_get_instr_info_reg2(vmx_instruction_info);
+ instr_info = vmx_get_instr_info();
+ gpr_index = vmx_get_instr_info_reg2(instr_info);
type = kvm_register_read(vcpu, gpr_index);
types = (vmx->nested.msrs.vpid_caps &
@@ -6106,7 +6106,7 @@ static int handle_invvpid(struct kvm_vcpu *vcpu)
* operand is read even if it isn't needed (e.g., for type==global)
*/
if (get_vmx_mem_address(vcpu, vmx_get_exit_qual(vcpu),
- vmx_instruction_info, false, sizeof(operand), &gva))
+ instr_info, false, sizeof(operand), &gva))
return 1;
r = kvm_read_guest_virt(vcpu, gva, &operand, sizeof(operand), &e);
if (r != X86EMUL_CONTINUE)
@@ -6436,16 +6436,16 @@ static bool nested_vmx_exit_handled_encls(struct kvm_vcpu *vcpu,
static bool nested_vmx_exit_handled_vmcs_access(struct kvm_vcpu *vcpu,
struct vmcs12 *vmcs12, gpa_t bitmap)
{
- u64 vmx_instruction_info;
unsigned long field;
+ u64 instr_info;
u8 b;
if (!nested_cpu_has_shadow_vmcs(vmcs12))
return true;
/* Decode instruction info and find the field to access */
- vmx_instruction_info = vmx_get_instr_info();
- field = kvm_register_read(vcpu, (((vmx_instruction_info) >> 28) & 0xf));
+ instr_info = vmx_get_instr_info();
+ field = kvm_register_read(vcpu, vmx_get_instr_info_reg2(instr_info));
/* Out-of-range fields always cause a VM exit from L2 to L1 */
if (field >> 15)
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 74c4b9dbe29b..bd8505d69420 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -6154,8 +6154,8 @@ static int handle_monitor_trap(struct kvm_vcpu *vcpu)
static int handle_invpcid(struct kvm_vcpu *vcpu)
{
- u64 vmx_instruction_info;
unsigned long type;
+ u64 instr_info;
gva_t gva;
struct {
u64 pcid;
@@ -6168,16 +6168,15 @@ static int handle_invpcid(struct kvm_vcpu *vcpu)
return 1;
}
- vmx_instruction_info = vmx_get_instr_info();
- gpr_index = vmx_get_instr_info_reg2(vmx_instruction_info);
+ instr_info = vmx_get_instr_info();
+ gpr_index = vmx_get_instr_info_reg2(instr_info);
type = kvm_register_read(vcpu, gpr_index);
/* According to the Intel instruction reference, the memory operand
* is read even if it isn't needed (e.g., for type==all)
*/
if (get_vmx_mem_address(vcpu, vmx_get_exit_qual(vcpu),
- vmx_instruction_info, false,
- sizeof(operand), &gva))
+ instr_info, false, sizeof(operand), &gva))
return 1;
return kvm_handle_invpcid(vcpu, type, gva);
diff --git a/arch/x86/kvm/vmx/vmx.h b/arch/x86/kvm/vmx/vmx.h
index 4751f27f006b..92eed58f48ad 100644
--- a/arch/x86/kvm/vmx/vmx.h
+++ b/arch/x86/kvm/vmx/vmx.h
@@ -715,14 +715,54 @@ static inline u64 vmx_get_instr_info(void)
return vmcs_read32(VMX_INSTRUCTION_INFO);
}
-static inline int vmx_get_instr_info_reg(u64 vmx_instr_info)
+static inline int vmx_get_instr_info_reg(u64 instr_info)
{
- return (vmx_instr_info >> 3) & 0xf;
+ return (instr_info >> 3) & 0xf;
}
-static inline int vmx_get_instr_info_reg2(u64 vmx_instr_info)
+static inline int vmx_get_instr_info_reg2(u64 instr_info)
{
- return (vmx_instr_info >> 28) & 0xf;
+ return (instr_info >> 28) & 0xf;
+}
+
+static inline int vmx_get_instr_info_scaling(u64 instr_info)
+{
+ return instr_info & 3;
+}
+
+static inline int vmx_get_instr_info_addr_size(u64 instr_info)
+{
+ return (instr_info >> 7) & 7;
+}
+
+static inline bool vmx_get_instr_info_is_reg(u64 instr_info)
+{
+ return !!(instr_info & BIT(10));
+}
+
+static inline int vmx_get_instr_info_seg_reg(u64 instr_info)
+{
+ return (instr_info >> 15) & 7;
+}
+
+static inline int vmx_get_instr_info_index_reg(u64 instr_info)
+{
+ return (instr_info >> 18) & 0xf;
+}
+
+static inline bool vmx_get_instr_info_index_is_valid(u64 instr_info)
+{
+ return !(instr_info & BIT(22));
+}
+
+static inline int vmx_get_instr_info_base_reg(u64 instr_info)
+{
+ return (instr_info >> 23) & 0xf;
+}
+
+static inline bool vmx_get_instr_info_base_is_valid(u64 instr_info)
+{
+ return !(instr_info & BIT(27));
}
static inline bool vmx_can_use_ipiv(struct kvm_vcpu *vcpu)
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v7 06/20] KVM: VMX: Remove unused control-register access defines
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (4 preceding siblings ...)
2026-09-02 2:17 ` [PATCH v7 05/20] KVM: VMX: Refactor instruction information decoding Chang S. Bae
@ 2026-09-02 2:17 ` Chang S. Bae
2026-09-02 2:17 ` [PATCH v7 07/20] KVM: VMX: Refactor register index retrieval from exit qualification Chang S. Bae
` (13 subsequent siblings)
19 siblings, 0 replies; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:17 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
CONTROL_REG_ACCESS_REG becomes misleading with EGPRs. Rather than leave
stale, unused definitions behind, remove them all.
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
arch/x86/include/asm/vmx.h | 19 -------------------
1 file changed, 19 deletions(-)
diff --git a/arch/x86/include/asm/vmx.h b/arch/x86/include/asm/vmx.h
index 3f1b3096ff04..68835ad9af71 100644
--- a/arch/x86/include/asm/vmx.h
+++ b/arch/x86/include/asm/vmx.h
@@ -445,27 +445,8 @@ enum vmcs_field {
/*
* Exit Qualifications for MOV for Control Register Access
*/
-#define CONTROL_REG_ACCESS_NUM 0x7 /* 2:0, number of control reg.*/
-#define CONTROL_REG_ACCESS_TYPE 0x30 /* 5:4, access type */
-#define CONTROL_REG_ACCESS_REG 0xf00 /* 10:8, general purpose reg. */
#define LMSW_SOURCE_DATA_SHIFT 16
#define LMSW_SOURCE_DATA (0xFFFF << LMSW_SOURCE_DATA_SHIFT) /* 16:31 lmsw source */
-#define REG_EAX (0 << 8)
-#define REG_ECX (1 << 8)
-#define REG_EDX (2 << 8)
-#define REG_EBX (3 << 8)
-#define REG_ESP (4 << 8)
-#define REG_EBP (5 << 8)
-#define REG_ESI (6 << 8)
-#define REG_EDI (7 << 8)
-#define REG_R8 (8 << 8)
-#define REG_R9 (9 << 8)
-#define REG_R10 (10 << 8)
-#define REG_R11 (11 << 8)
-#define REG_R12 (12 << 8)
-#define REG_R13 (13 << 8)
-#define REG_R14 (14 << 8)
-#define REG_R15 (15 << 8)
/*
* Exit Qualifications for MOV for Debug Register Access
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v7 07/20] KVM: VMX: Refactor register index retrieval from exit qualification
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (5 preceding siblings ...)
2026-09-02 2:17 ` [PATCH v7 06/20] KVM: VMX: Remove unused control-register access defines Chang S. Bae
@ 2026-09-02 2:17 ` Chang S. Bae
2026-09-02 2:17 ` [PATCH v7 08/20] KVM: VMX: Support instruction information extension Chang S. Bae
` (12 subsequent siblings)
19 siblings, 0 replies; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:17 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
Introduce a helper to extract the register index from the VMX exit
qualification field.
In addition to the VMX instruction information field, exit qualification
also encodes a register index. This field will expand into the previously
reserved bit for extended register IDs. This refactoring will simplify
the extended register handling without code duplication.
No functional change intended.
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
arch/x86/include/asm/vmx.h | 1 -
arch/x86/kvm/vmx/nested.c | 2 +-
arch/x86/kvm/vmx/vmx.c | 4 ++--
arch/x86/kvm/vmx/vmx.h | 5 +++++
4 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/arch/x86/include/asm/vmx.h b/arch/x86/include/asm/vmx.h
index 68835ad9af71..f98e40df5004 100644
--- a/arch/x86/include/asm/vmx.h
+++ b/arch/x86/include/asm/vmx.h
@@ -455,7 +455,6 @@ enum vmcs_field {
#define DEBUG_REG_ACCESS_TYPE 0x10 /* 4, direction of access */
#define TYPE_MOV_TO_DR (0 << 4)
#define TYPE_MOV_FROM_DR (1 << 4)
-#define DEBUG_REG_ACCESS_REG(eq) (((eq) >> 8) & 0xf) /* 11:8, general purpose reg. */
/*
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index f05ac3c9aa12..7229934c38bb 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -6358,7 +6358,7 @@ static bool nested_vmx_exit_handled_cr(struct kvm_vcpu *vcpu,
switch ((exit_qualification >> 4) & 3) {
case 0: /* mov to cr */
- reg = (exit_qualification >> 8) & 15;
+ reg = vmx_get_exit_qual_reg(exit_qualification);
val = kvm_register_read(vcpu, reg);
switch (cr) {
case 0:
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index bd8505d69420..ee975a1ae42f 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -5698,7 +5698,7 @@ static int handle_cr(struct kvm_vcpu *vcpu)
exit_qualification = vmx_get_exit_qual(vcpu);
cr = exit_qualification & 15;
- reg = (exit_qualification >> 8) & 15;
+ reg = vmx_get_exit_qual_reg(exit_qualification);
switch ((exit_qualification >> 4) & 3) {
case 0: /* mov to cr */
val = kvm_register_read(vcpu, reg);
@@ -5816,7 +5816,7 @@ static int handle_dr(struct kvm_vcpu *vcpu)
return 1;
}
- reg = DEBUG_REG_ACCESS_REG(exit_qualification);
+ reg = vmx_get_exit_qual_reg(exit_qualification);
if (exit_qualification & TYPE_MOV_FROM_DR) {
kvm_register_write(vcpu, reg, kvm_get_dr(vcpu, dr));
err = 0;
diff --git a/arch/x86/kvm/vmx/vmx.h b/arch/x86/kvm/vmx/vmx.h
index 92eed58f48ad..863b08478544 100644
--- a/arch/x86/kvm/vmx/vmx.h
+++ b/arch/x86/kvm/vmx/vmx.h
@@ -330,6 +330,11 @@ static __always_inline unsigned long vmx_get_exit_qual(struct kvm_vcpu *vcpu)
return vt->exit_qualification;
}
+static inline int vmx_get_exit_qual_reg(unsigned long exit_qualification)
+{
+ return (exit_qualification >> 8) & 0xf;
+}
+
static __always_inline u32 vmx_get_intr_info(struct kvm_vcpu *vcpu)
{
struct vcpu_vt *vt = to_vt(vcpu);
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v7 08/20] KVM: VMX: Support instruction information extension
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (6 preceding siblings ...)
2026-09-02 2:17 ` [PATCH v7 07/20] KVM: VMX: Refactor register index retrieval from exit qualification Chang S. Bae
@ 2026-09-02 2:17 ` Chang S. Bae
2026-09-02 2:17 ` [PATCH v7 09/20] KVM: nVMX: Propagate extended instruction information Chang S. Bae
` (11 subsequent siblings)
19 siblings, 0 replies; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:17 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
Define the VMCS field offset for the extended instruction information.
Then, support 5-bit register indices retrieval from VMCS fields.
Note the APX enumeration alone indicates the extension is available.
For safety, do not assume that previously reserved bits were zero
on older implementations.
Suggested-by: Paolo Bonzini <pbonzini@redhat.com>
Suggested-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
Link: https://lore.kernel.org/aakEsXJgO-3m2xca@google.com
---
V6 -> V7: Use cpu_feature_enabled() instead, as rebased on v7.3
Note for the review of extensions in following functions:
+-------+------------------+------------------------------------+
| Bits | Field Name | Related KVM Functions |
+=======+==================+====================================+
| 3:2 | ASIZE | vmx_get_instr_info_addr_size() |
+-------+------------------+------------------------------------+
| 4 | Mem/Reg (1=>reg) | vmx_get_instr_info_is_reg() |
+-------+------------------+------------------------------------+
| 9:7 | Segment | vmx_get_instr_info_seg_reg() |
+-------+------------------+------------------------------------+
| 10 | IndexInvalid | vmx_get_instr_info_base_reg() |
+-------+------------------+------------------------------------+
| 11 | BaseInvalid | vmx_get_instr_info_base_is_valid() |
+-------+------------------+------------------------------------+
| 20:16 | Reg1 | vmx_get_instr_info_reg() |
+-------+------------------+------------------------------------+
| 28:24 | Index | vmx_get_instr_info_index_reg() |
+-------+------------------+------------------------------------+
| 36:32 | Base | vmx_get_instr_info_base_reg() |
+-------+------------------+------------------------------------+
| 44:40 | Reg2 | vmx_get_instr_info_reg2() |
+-------+------------------+------------------------------------+
Reference:
APX Specification, Table 3.11 Extended Instruction-Information (EII)
VMCS Field
---
arch/x86/include/asm/vmx.h | 2 ++
arch/x86/kvm/vmx/vmx.h | 42 +++++++++++++++++++++++++++-----------
2 files changed, 32 insertions(+), 12 deletions(-)
diff --git a/arch/x86/include/asm/vmx.h b/arch/x86/include/asm/vmx.h
index f98e40df5004..0b1e2353db3b 100644
--- a/arch/x86/include/asm/vmx.h
+++ b/arch/x86/include/asm/vmx.h
@@ -277,6 +277,8 @@ enum vmcs_field {
PID_POINTER_TABLE_HIGH = 0x00002043,
GUEST_PHYSICAL_ADDRESS = 0x00002400,
GUEST_PHYSICAL_ADDRESS_HIGH = 0x00002401,
+ EXTENDED_INSTRUCTION_INFO = 0x00002406,
+ EXTENDED_INSTRUCTION_INFO_HIGH = 0x00002407,
VMCS_LINK_POINTER = 0x00002800,
VMCS_LINK_POINTER_HIGH = 0x00002801,
GUEST_IA32_DEBUGCTL = 0x00002802,
diff --git a/arch/x86/kvm/vmx/vmx.h b/arch/x86/kvm/vmx/vmx.h
index 863b08478544..4a76af613720 100644
--- a/arch/x86/kvm/vmx/vmx.h
+++ b/arch/x86/kvm/vmx/vmx.h
@@ -330,9 +330,18 @@ static __always_inline unsigned long vmx_get_exit_qual(struct kvm_vcpu *vcpu)
return vt->exit_qualification;
}
+/*
+ * The APX enumeration guarantees the presence of the extended fields.
+ * The host CPUID bit alone is sufficient to rely on it.
+ */
+static inline bool vmx_instr_info_extended(void)
+{
+ return cpu_feature_enabled(X86_FEATURE_APX);
+}
+
static inline int vmx_get_exit_qual_reg(unsigned long exit_qualification)
{
- return (exit_qualification >> 8) & 0xf;
+ return (exit_qualification >> 8) & (vmx_instr_info_extended() ? 0x1f : 0xf);
}
static __always_inline u32 vmx_get_intr_info(struct kvm_vcpu *vcpu)
@@ -714,20 +723,22 @@ static inline bool vmx_guest_state_valid(struct kvm_vcpu *vcpu)
void dump_vmcs(struct kvm_vcpu *vcpu);
-/* A placeholder to smoothen 64-bit extension */
static inline u64 vmx_get_instr_info(void)
{
- return vmcs_read32(VMX_INSTRUCTION_INFO);
+ return vmx_instr_info_extended() ? vmcs_read64(EXTENDED_INSTRUCTION_INFO) :
+ vmcs_read32(VMX_INSTRUCTION_INFO);
}
static inline int vmx_get_instr_info_reg(u64 instr_info)
{
- return (instr_info >> 3) & 0xf;
+ return vmx_instr_info_extended() ? (instr_info >> 16) & 0x1f :
+ (instr_info >> 3) & 0xf;
}
static inline int vmx_get_instr_info_reg2(u64 instr_info)
{
- return (instr_info >> 28) & 0xf;
+ return vmx_instr_info_extended() ? (instr_info >> 40) & 0x1f :
+ (instr_info >> 28) & 0xf;
}
static inline int vmx_get_instr_info_scaling(u64 instr_info)
@@ -737,37 +748,44 @@ static inline int vmx_get_instr_info_scaling(u64 instr_info)
static inline int vmx_get_instr_info_addr_size(u64 instr_info)
{
- return (instr_info >> 7) & 7;
+ return vmx_instr_info_extended() ? (instr_info >> 2) & 3 :
+ (instr_info >> 7) & 7;
}
static inline bool vmx_get_instr_info_is_reg(u64 instr_info)
{
- return !!(instr_info & BIT(10));
+ return vmx_instr_info_extended() ? !!(instr_info & BIT(4)) :
+ !!(instr_info & BIT(10));
}
static inline int vmx_get_instr_info_seg_reg(u64 instr_info)
{
- return (instr_info >> 15) & 7;
+ return vmx_instr_info_extended() ? (instr_info >> 7) & 7 :
+ (instr_info >> 15) & 7;
}
static inline int vmx_get_instr_info_index_reg(u64 instr_info)
{
- return (instr_info >> 18) & 0xf;
+ return vmx_instr_info_extended() ? (instr_info >> 24) & 0x1f :
+ (instr_info >> 18) & 0xf;
}
static inline bool vmx_get_instr_info_index_is_valid(u64 instr_info)
{
- return !(instr_info & BIT(22));
+ return vmx_instr_info_extended() ? !(instr_info & BIT(10)) :
+ !(instr_info & BIT(22));
}
static inline int vmx_get_instr_info_base_reg(u64 instr_info)
{
- return (instr_info >> 23) & 0xf;
+ return vmx_instr_info_extended() ? (instr_info >> 32) & 0x1f :
+ (instr_info >> 23) & 0xf;
}
static inline bool vmx_get_instr_info_base_is_valid(u64 instr_info)
{
- return !(instr_info & BIT(27));
+ return vmx_instr_info_extended() ? !(instr_info & BIT(11)) :
+ !(instr_info & BIT(27));
}
static inline bool vmx_can_use_ipiv(struct kvm_vcpu *vcpu)
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v7 09/20] KVM: nVMX: Propagate extended instruction information
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (7 preceding siblings ...)
2026-09-02 2:17 ` [PATCH v7 08/20] KVM: VMX: Support instruction information extension Chang S. Bae
@ 2026-09-02 2:17 ` Chang S. Bae
2026-09-02 3:07 ` sashiko-bot
2026-09-02 2:17 ` [PATCH v7 10/20] KVM: x86: Support EGPR accessing and tracking for emulator Chang S. Bae
` (10 subsequent siblings)
19 siblings, 1 reply; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:17 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae, Chao Gao
Define the new extended_instruction_info field in struct vmcs12 and
propagate it to nested VMX. Gate the propagation on the guest APX
enumeration, which aligns with bare metal behavior. Thus, define the
CPUID bit here too.
Suggested-by: Chao Gao <chao.gao@intel.com>
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
Link: https://lore.kernel.org/aRvOSnaUt1E+%2FpkC@intel.com
---
arch/x86/kvm/reverse_cpuid.h | 2 ++
arch/x86/kvm/vmx/nested.c | 6 ++++++
arch/x86/kvm/vmx/vmcs12.c | 1 +
arch/x86/kvm/vmx/vmcs12.h | 3 ++-
4 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/reverse_cpuid.h b/arch/x86/kvm/reverse_cpuid.h
index 2ad25781cefb..80b41d5559da 100644
--- a/arch/x86/kvm/reverse_cpuid.h
+++ b/arch/x86/kvm/reverse_cpuid.h
@@ -37,6 +37,7 @@
#define X86_FEATURE_AVX_VNNI_INT16 KVM_X86_FEATURE(CPUID_7_1_EDX, 10)
#define X86_FEATURE_PREFETCHITI KVM_X86_FEATURE(CPUID_7_1_EDX, 14)
#define X86_FEATURE_AVX10 KVM_X86_FEATURE(CPUID_7_1_EDX, 19)
+#define KVM_X86_FEATURE_APX KVM_X86_FEATURE(CPUID_7_1_EDX, 21)
/* Intel-defined sub-features, CPUID level 0x00000007:2 (EDX) */
#define X86_FEATURE_INTEL_PSFD KVM_X86_FEATURE(CPUID_7_2_EDX, 0)
@@ -146,6 +147,7 @@ static __always_inline u32 __feature_translate(int x86_feature)
KVM_X86_TRANSLATE_FEATURE(SGX1);
KVM_X86_TRANSLATE_FEATURE(SGX2);
KVM_X86_TRANSLATE_FEATURE(SGX_EDECCSSA);
+ KVM_X86_TRANSLATE_FEATURE(APX);
KVM_X86_TRANSLATE_FEATURE(CONSTANT_TSC);
KVM_X86_TRANSLATE_FEATURE(PERFMON_V2);
KVM_X86_TRANSLATE_FEATURE(RRSBA_CTRL);
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 7229934c38bb..69302b806a98 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -4773,6 +4773,12 @@ static void prepare_vmcs12(struct kvm_vcpu *vcpu, struct vmcs12 *vmcs12,
vmcs12->vm_exit_intr_info = exit_intr_info;
vmcs12->vm_exit_instruction_len = exit_insn_len;
vmcs12->vmx_instruction_info = vmcs_read32(VMX_INSTRUCTION_INFO);
+ /*
+ * The APX enumeration guarantees the presence of the extended
+ * fields. This CPUID bit alone is sufficient to rely on it.
+ */
+ if (guest_cpu_cap_has(vcpu, X86_FEATURE_APX))
+ vmcs12->extended_instruction_info = vmcs_read64(EXTENDED_INSTRUCTION_INFO);
/*
* According to spec, there's no need to store the guest's
diff --git a/arch/x86/kvm/vmx/vmcs12.c b/arch/x86/kvm/vmx/vmcs12.c
index 1ebe67c384ad..267aa64f005e 100644
--- a/arch/x86/kvm/vmx/vmcs12.c
+++ b/arch/x86/kvm/vmx/vmcs12.c
@@ -53,6 +53,7 @@ static const u16 kvm_supported_vmcs12_field_offsets[] __initconst = {
FIELD64(XSS_EXIT_BITMAP, xss_exit_bitmap),
FIELD64(ENCLS_EXITING_BITMAP, encls_exiting_bitmap),
FIELD64(GUEST_PHYSICAL_ADDRESS, guest_physical_address),
+ FIELD64(EXTENDED_INSTRUCTION_INFO, extended_instruction_info),
FIELD64(VMCS_LINK_POINTER, vmcs_link_pointer),
FIELD64(GUEST_IA32_DEBUGCTL, guest_ia32_debugctl),
FIELD64(GUEST_IA32_PAT, guest_ia32_pat),
diff --git a/arch/x86/kvm/vmx/vmcs12.h b/arch/x86/kvm/vmx/vmcs12.h
index 21cd1b75e4fd..25e9a41c248f 100644
--- a/arch/x86/kvm/vmx/vmcs12.h
+++ b/arch/x86/kvm/vmx/vmcs12.h
@@ -71,7 +71,7 @@ struct __packed vmcs12 {
u64 pml_address;
u64 encls_exiting_bitmap;
u64 tsc_multiplier;
- u64 padding64[1]; /* room for future expansion */
+ u64 extended_instruction_info;
/*
* To allow migration of L1 (complete with its L2 guests) between
* machines of different natural widths (32 or 64 bit), we cannot have
@@ -261,6 +261,7 @@ static inline void vmx_check_vmcs12_offsets(void)
CHECK_OFFSET(pml_address, 312);
CHECK_OFFSET(encls_exiting_bitmap, 320);
CHECK_OFFSET(tsc_multiplier, 328);
+ CHECK_OFFSET(extended_instruction_info, 336);
CHECK_OFFSET(cr0_guest_host_mask, 344);
CHECK_OFFSET(cr4_guest_host_mask, 352);
CHECK_OFFSET(cr0_read_shadow, 360);
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v7 09/20] KVM: nVMX: Propagate extended instruction information
2026-09-02 2:17 ` [PATCH v7 09/20] KVM: nVMX: Propagate extended instruction information Chang S. Bae
@ 2026-09-02 3:07 ` sashiko-bot
0 siblings, 0 replies; 29+ messages in thread
From: sashiko-bot @ 2026-09-02 3:07 UTC (permalink / raw)
To: Chang S. Bae; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Missing Support for EVMCS Field / Reachable WARN_ON
--
commit 24f2192a997da0f16691df1c9b88d4d8d59b8258
Author: Chang S. Bae <chang.seok.bae@intel.com>
KVM: nVMX: Propagate extended instruction information
Define the new extended_instruction_info field in struct vmcs12 and
propagate it to nested VMX. Gate the propagation on the guest APX
enumeration, which aligns with bare metal behavior.
Link: https://lore.kernel.org/aRvOSnaUt1E+%2FpkC@intel.com
[ ... ]
> diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
> index 7229934c38bb1..69302b806a98e 100644
> --- a/arch/x86/kvm/vmx/nested.c
> +++ b/arch/x86/kvm/vmx/nested.c
> @@ -4773,6 +4773,12 @@ static void prepare_vmcs12(struct kvm_vcpu *vcpu, struct vmcs12 *vmcs12,
> vmcs12->vm_exit_intr_info = exit_intr_info;
> vmcs12->vm_exit_instruction_len = exit_insn_len;
> vmcs12->vmx_instruction_info = vmcs_read32(VMX_INSTRUCTION_INFO);
> + /*
> + * The APX enumeration guarantees the presence of the extended
> + * fields. This CPUID bit alone is sufficient to rely on it.
> + */
> + if (guest_cpu_cap_has(vcpu, X86_FEATURE_APX))
> + vmcs12->extended_instruction_info = vmcs_read64(EXTENDED_INSTRUCTION_INFO);
[Severity: High]
Could this lead to a host-side Denial of Service when KVM is running as a
nested hypervisor on top of Hyper-V?
When EVMCS is active (kvm_is_using_evmcs() returns true) and the L2 guest
has the APX feature, prepare_vmcs12() will unconditionally execute
vmcs_read64(EXTENDED_INSTRUCTION_INFO).
Since EXTENDED_INSTRUCTION_INFO does not appear to be present in the EVMCS
mapping array (vmcs_field_to_evmcs_1), won't the subsequent call to
get_evmcs_offset() return -ENOENT and trigger a WARN_ONCE?
If panic_on_warn is enabled, an unprivileged L2 guest could potentially
trigger a host panic just by issuing a nested VM-exit.
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260902021804.718676-1-chang.seok.bae@intel.com?part=9
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v7 10/20] KVM: x86: Support EGPR accessing and tracking for emulator
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (8 preceding siblings ...)
2026-09-02 2:17 ` [PATCH v7 09/20] KVM: nVMX: Propagate extended instruction information Chang S. Bae
@ 2026-09-02 2:17 ` Chang S. Bae
2026-09-02 2:17 ` [PATCH v7 11/20] KVM: x86: Handle EGPR index and REX2-incompatible opcodes Chang S. Bae
` (9 subsequent siblings)
19 siblings, 0 replies; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:17 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
Extend the emulator context and GPR accessors to handle EGPRs before
adding support for REX2-prefixed instructions. Like VCPU cache, the
emulator can uniformly cache and track all GPRs.
Also replace `1 << reg` with `BIT(reg)` in reg_read()/reg_write(). The
former performs a signed 32-bit shift, which becomes undefined for R31.
BIT() instead shifts an unsigned value that is suitable for all indices.
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
arch/x86/kvm/kvm_emulate.h | 20 +++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/arch/x86/kvm/kvm_emulate.h b/arch/x86/kvm/kvm_emulate.h
index 3e375af15c03..b1498f618c81 100644
--- a/arch/x86/kvm/kvm_emulate.h
+++ b/arch/x86/kvm/kvm_emulate.h
@@ -109,13 +109,13 @@ struct x86_instruction_info {
struct x86_emulate_ops {
void (*vm_bugged)(struct x86_emulate_ctxt *ctxt);
/*
- * read_gpr: read a general purpose register (rax - r15)
+ * read_gpr: read a general purpose register (rax - r31)
*
* @reg: gpr number.
*/
ulong (*read_gpr)(struct x86_emulate_ctxt *ctxt, unsigned reg);
/*
- * write_gpr: write a general purpose register (rax - r15)
+ * write_gpr: write a general purpose register (rax - r31)
*
* @reg: gpr number.
* @val: value to write.
@@ -321,7 +321,9 @@ typedef void (*fastop_t)(struct fastop *);
* also uses _eip, RIP cannot be a register operand nor can it be an operand in
* a ModRM or SIB byte.
*/
-#ifdef CONFIG_X86_64
+#if defined(CONFIG_KVM_APX)
+#define NR_EMULATOR_GPRS 32
+#elif defined(CONFIG_X86_64)
#define NR_EMULATOR_GPRS 16
#else
#define NR_EMULATOR_GPRS 8
@@ -381,9 +383,9 @@ struct x86_emulate_ctxt {
u8 lock_prefix;
u8 rep_prefix;
/* bitmaps of registers in _regs[] that can be read */
- u16 regs_valid;
+ u32 regs_valid;
/* bitmaps of registers in _regs[] that have been written */
- u16 regs_dirty;
+ u32 regs_dirty;
/* modrm */
u8 modrm;
u8 modrm_mod;
@@ -547,8 +549,8 @@ static inline ulong reg_read(struct x86_emulate_ctxt *ctxt, unsigned nr)
if (KVM_EMULATOR_BUG_ON(nr >= NR_EMULATOR_GPRS, ctxt))
nr &= NR_EMULATOR_GPRS - 1;
- if (!(ctxt->regs_valid & (1 << nr))) {
- ctxt->regs_valid |= 1 << nr;
+ if (!(ctxt->regs_valid & BIT(nr))) {
+ ctxt->regs_valid |= BIT(nr);
ctxt->_regs[nr] = ctxt->ops->read_gpr(ctxt, nr);
}
return ctxt->_regs[nr];
@@ -562,8 +564,8 @@ static inline ulong *reg_write(struct x86_emulate_ctxt *ctxt, unsigned nr)
BUILD_BUG_ON(sizeof(ctxt->regs_dirty) * BITS_PER_BYTE < NR_EMULATOR_GPRS);
BUILD_BUG_ON(sizeof(ctxt->regs_valid) * BITS_PER_BYTE < NR_EMULATOR_GPRS);
- ctxt->regs_valid |= 1 << nr;
- ctxt->regs_dirty |= 1 << nr;
+ ctxt->regs_valid |= BIT(nr);
+ ctxt->regs_dirty |= BIT(nr);
return &ctxt->_regs[nr];
}
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v7 11/20] KVM: x86: Handle EGPR index and REX2-incompatible opcodes
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (9 preceding siblings ...)
2026-09-02 2:17 ` [PATCH v7 10/20] KVM: x86: Support EGPR accessing and tracking for emulator Chang S. Bae
@ 2026-09-02 2:17 ` Chang S. Bae
2026-09-02 3:06 ` sashiko-bot
2026-09-02 2:17 ` [PATCH v7 12/20] KVM: x86: Support REX2-prefixed opcode decode Chang S. Bae
` (8 subsequent siblings)
19 siblings, 1 reply; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:17 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
Prepare the emulator for REX2 handling by introducing the NoRex2 opcode
flag and supporting extended register indices. For the latter, factor out
common logic for calculating register IDs.
REX2 does not support three-byte opcodes. Instead, the REX2.M bit selects
between one- and two-byte opcode tables, which were previously
distinguished by the 0x0F escape byte.
Some legacy instructions in those tables never reference extended
registers. When prefixed with REX, such instructions are treated as if
the prefix were absent. In contrast, a REX2 prefix causes a #UD, which
should be handled explicitly.
Suggested-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
Link: https://lore.kernel.org/1ebf3a23-5671-41c1-8daa-c83f2f105936@redhat.com
---
arch/x86/kvm/emulate.c | 80 +++++++++++++++++++++++---------------
arch/x86/kvm/kvm_emulate.h | 1 +
2 files changed, 50 insertions(+), 31 deletions(-)
diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c
index c1b21282187f..a1a567fd0378 100644
--- a/arch/x86/kvm/emulate.c
+++ b/arch/x86/kvm/emulate.c
@@ -176,6 +176,7 @@
#define TwoMemOp ((u64)1 << 55) /* Instruction has two memory operand */
#define IsBranch ((u64)1 << 56) /* Instruction is considered a branch. */
#define ShadowStack ((u64)1 << 57) /* Instruction affects Shadow Stacks. */
+#define NoRex2 ((u64)1 << 58) /* Instruction not present in REX2 maps */
#define DstXacc (DstAccLo | SrcAccHi | SrcWrite)
@@ -245,6 +246,7 @@ enum rex_bits {
REX_X = 2,
REX_R = 4,
REX_W = 8,
+ REX_M = 0x80,
};
static void writeback_registers(struct x86_emulate_ctxt *ctxt)
@@ -1061,6 +1063,15 @@ static int em_fnstsw(struct x86_emulate_ctxt *ctxt)
return X86EMUL_CONTINUE;
}
+static __always_inline int rex_get_rxb(u8 rex, u8 fld)
+{
+ BUILD_BUG_ON(!__builtin_constant_p(fld));
+ BUILD_BUG_ON(fld != REX_B && fld != REX_X && fld != REX_R);
+
+ rex >>= ffs(fld) - 1;
+ return (rex & 1 ? 8 : 0) + (rex & 0x10 ? 16 : 0);
+}
+
static void __decode_register_operand(struct x86_emulate_ctxt *ctxt,
struct operand *op, int reg)
{
@@ -1100,7 +1111,7 @@ static void decode_register_operand(struct x86_emulate_ctxt *ctxt,
if (ctxt->d & ModRM)
reg = ctxt->modrm_reg;
else
- reg = (ctxt->b & 7) | (ctxt->rex_bits & REX_B ? 8 : 0);
+ reg = (ctxt->b & 7) | rex_get_rxb(ctxt->rex_bits, REX_B);
__decode_register_operand(ctxt, op, reg);
}
@@ -1119,9 +1130,9 @@ static int decode_modrm(struct x86_emulate_ctxt *ctxt,
int rc = X86EMUL_CONTINUE;
ulong modrm_ea = 0;
- ctxt->modrm_reg = (ctxt->rex_bits & REX_R ? 8 : 0);
- index_reg = (ctxt->rex_bits & REX_X ? 8 : 0);
- base_reg = (ctxt->rex_bits & REX_B ? 8 : 0);
+ ctxt->modrm_reg = rex_get_rxb(ctxt->rex_bits, REX_R);
+ index_reg = rex_get_rxb(ctxt->rex_bits, REX_X);
+ base_reg = rex_get_rxb(ctxt->rex_bits, REX_B);
ctxt->modrm_mod = (ctxt->modrm & 0xc0) >> 6;
ctxt->modrm_reg |= (ctxt->modrm & 0x38) >> 3;
@@ -4255,7 +4266,7 @@ static const struct opcode opcode_table[256] = {
/* 0x38 - 0x3F */
I6ALU(NoWrite, em_cmp), N, N,
/* 0x40 - 0x4F */
- X8(I(DstReg, em_inc)), X8(I(DstReg, em_dec)),
+ X8(I(DstReg | NoRex2, em_inc)), X8(I(DstReg | NoRex2, em_dec)),
/* 0x50 - 0x57 */
X8(I(SrcReg | Stack, em_push)),
/* 0x58 - 0x5F */
@@ -4273,7 +4284,7 @@ static const struct opcode opcode_table[256] = {
I2bvIP(DstDI | SrcDX | Mov | String | Unaligned, em_in, ins, check_perm_in), /* insb, insw/insd */
I2bvIP(SrcSI | DstDX | String, em_out, outs, check_perm_out), /* outsb, outsw/outsd */
/* 0x70 - 0x7F */
- X16(D(SrcImmByte | NearBranch | IsBranch)),
+ X16(D(SrcImmByte | NearBranch | IsBranch | NoRex2)),
/* 0x80 - 0x87 */
G(ByteOp | DstMem | SrcImm, group1),
G(DstMem | SrcImm, group1),
@@ -4297,15 +4308,15 @@ static const struct opcode opcode_table[256] = {
II(ImplicitOps | Stack, em_popf, popf),
I(ImplicitOps, em_sahf), I(ImplicitOps, em_lahf),
/* 0xA0 - 0xA7 */
- I2bv(DstAcc | SrcMem | Mov | MemAbs, em_mov),
- I2bv(DstMem | SrcAcc | Mov | MemAbs | PageTable, em_mov),
- I2bv(SrcSI | DstDI | Mov | String | TwoMemOp, em_mov),
- I2bv(SrcSI | DstDI | String | NoWrite | TwoMemOp, em_cmp_r),
+ I2bv(DstAcc | SrcMem | Mov | MemAbs | NoRex2, em_mov),
+ I2bv(DstMem | SrcAcc | Mov | MemAbs | PageTable | NoRex2, em_mov),
+ I2bv(SrcSI | DstDI | Mov | String | TwoMemOp | NoRex2, em_mov),
+ I2bv(SrcSI | DstDI | String | NoWrite | TwoMemOp | NoRex2, em_cmp_r),
/* 0xA8 - 0xAF */
- I2bv(DstAcc | SrcImm | NoWrite, em_test),
- I2bv(SrcAcc | DstDI | Mov | String, em_mov),
- I2bv(SrcSI | DstAcc | Mov | String, em_mov),
- I2bv(SrcAcc | DstDI | String | NoWrite, em_cmp_r),
+ I2bv(DstAcc | SrcImm | NoWrite | NoRex2, em_test),
+ I2bv(SrcAcc | DstDI | Mov | String | NoRex2, em_mov),
+ I2bv(SrcSI | DstAcc | Mov | String | NoRex2, em_mov),
+ I2bv(SrcAcc | DstDI | String | NoWrite | NoRex2, em_cmp_r),
/* 0xB0 - 0xB7 */
X8(I(ByteOp | DstReg | SrcImm | Mov, em_mov)),
/* 0xB8 - 0xBF */
@@ -4335,17 +4346,17 @@ static const struct opcode opcode_table[256] = {
/* 0xD8 - 0xDF */
N, E(0, &escape_d9), N, E(0, &escape_db), N, E(0, &escape_dd), N, N,
/* 0xE0 - 0xE7 */
- X3(I(SrcImmByte | NearBranch | IsBranch, em_loop)),
- I(SrcImmByte | NearBranch | IsBranch, em_jcxz),
- I2bvIP(SrcImmUByte | DstAcc, em_in, in, check_perm_in),
- I2bvIP(SrcAcc | DstImmUByte, em_out, out, check_perm_out),
+ X3(I(SrcImmByte | NearBranch | IsBranch | NoRex2, em_loop)),
+ I(SrcImmByte | NearBranch | IsBranch | NoRex2, em_jcxz),
+ I2bvIP(SrcImmUByte | DstAcc | NoRex2, em_in, in, check_perm_in),
+ I2bvIP(SrcAcc | DstImmUByte | NoRex2, em_out, out, check_perm_out),
/* 0xE8 - 0xEF */
- I(SrcImm | NearBranch | IsBranch | ShadowStack, em_call),
- D(SrcImm | ImplicitOps | NearBranch | IsBranch),
- I(SrcImmFAddr | No64 | IsBranch, em_jmp_far),
- D(SrcImmByte | ImplicitOps | NearBranch | IsBranch),
- I2bvIP(SrcDX | DstAcc, em_in, in, check_perm_in),
- I2bvIP(SrcAcc | DstDX, em_out, out, check_perm_out),
+ I(SrcImm | NearBranch | IsBranch | ShadowStack | NoRex2, em_call),
+ D(SrcImm | ImplicitOps | NearBranch | IsBranch | NoRex2),
+ I(SrcImmFAddr | No64 | IsBranch | NoRex2, em_jmp_far),
+ D(SrcImmByte | ImplicitOps | NearBranch | IsBranch | NoRex2),
+ I2bvIP(SrcDX | DstAcc | NoRex2, em_in, in, check_perm_in),
+ I2bvIP(SrcAcc | DstDX | NoRex2, em_out, out, check_perm_out),
/* 0xF0 - 0xF7 */
N, DI(ImplicitOps, icebp), N, N,
DI(ImplicitOps | Priv, hlt), D(ImplicitOps),
@@ -4385,12 +4396,12 @@ static const struct opcode twobyte_table[256] = {
N, GP(ModRM | DstMem | SrcReg | Mov | Sse | Avx, &pfx_0f_2b),
N, N, N, N,
/* 0x30 - 0x3F */
- II(ImplicitOps | Priv, em_wrmsr, wrmsr),
- IIP(ImplicitOps, em_rdtsc, rdtsc, check_rdtsc),
- 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),
+ II(ImplicitOps | Priv | NoRex2, em_wrmsr, wrmsr),
+ IIP(ImplicitOps | NoRex2, em_rdtsc, rdtsc, check_rdtsc),
+ II(ImplicitOps | Priv | NoRex2, em_rdmsr, rdmsr),
+ IIP(ImplicitOps | NoRex2, em_rdpmc, rdpmc, check_rdpmc),
+ I(ImplicitOps | EmulateOnUD | IsBranch | ShadowStack | NoRex2, em_sysenter),
+ I(ImplicitOps | Priv | EmulateOnUD | IsBranch | ShadowStack | NoRex2, em_sysexit),
N, N,
N, N, N, N, N, N, N, N,
/* 0x40 - 0x4F */
@@ -4408,7 +4419,7 @@ static const struct opcode twobyte_table[256] = {
N, N, N, N,
N, N, N, GP(SrcReg | DstMem | ModRM | Mov, &pfx_0f_6f_0f_7f),
/* 0x80 - 0x8F */
- X16(D(SrcImm | NearBranch | IsBranch)),
+ X16(D(SrcImm | NearBranch | IsBranch | NoRex2)),
/* 0x90 - 0x9F */
X16(D(ByteOp | DstMem | SrcNone | ModRM| Mov)),
/* 0xA0 - 0xA7 */
@@ -5001,6 +5012,13 @@ int x86_decode_insn(struct x86_emulate_ctxt *ctxt, void *insn, int insn_len, int
opcode = opcode_table[ctxt->b];
}
+ /*
+ * Instructions marked with NoRex2 ignore a legacy REX prefix, but #UD
+ * should be raised when prefixed with REX2.
+ */
+ if (opcode.flags & NoRex2 && ctxt->rex_prefix == REX2_PREFIX)
+ opcode.flags = Undefined;
+
if (opcode.flags & ModRM)
ctxt->modrm = insn_fetch(u8, ctxt);
diff --git a/arch/x86/kvm/kvm_emulate.h b/arch/x86/kvm/kvm_emulate.h
index b1498f618c81..f1ca1938af71 100644
--- a/arch/x86/kvm/kvm_emulate.h
+++ b/arch/x86/kvm/kvm_emulate.h
@@ -335,6 +335,7 @@ typedef void (*fastop_t)(struct fastop *);
enum rex_type {
REX_NONE,
REX_PREFIX,
+ REX2_PREFIX,
};
struct x86_emulate_ctxt {
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v7 11/20] KVM: x86: Handle EGPR index and REX2-incompatible opcodes
2026-09-02 2:17 ` [PATCH v7 11/20] KVM: x86: Handle EGPR index and REX2-incompatible opcodes Chang S. Bae
@ 2026-09-02 3:06 ` sashiko-bot
0 siblings, 0 replies; 29+ messages in thread
From: sashiko-bot @ 2026-09-02 3:06 UTC (permalink / raw)
To: Chang S. Bae; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] Host Denial of Service (Kernel Panic) triggered by a malicious guest executing REX2-prefixed legacy SSE instructions.
- [Medium] Incomplete instruction decoding (emulation mismatch) due to missing NoRex2 flags on legacy instructions.
--
commit 1208b4a28108287a431ce4eac1464f0588e5971c
Author: Chang S. Bae <chang.seok.bae@intel.com>
KVM: x86: Handle EGPR index and REX2-incompatible opcodes
Prepare the emulator for REX2 handling by introducing the NoRex2 opcode
flag and supporting extended register indices. For the latter, factor out
common logic for calculating register IDs.
Link: https://lore.kernel.org/1ebf3a23-5671-41c1-8daa-c83f2f105936@redhat.com
> diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c
> index c1b21282187f3..a1a567fd03780 100644
> --- a/arch/x86/kvm/emulate.c
> +++ b/arch/x86/kvm/emulate.c
[ ... ]
> @@ -4273,7 +4284,7 @@ static const struct opcode opcode_table[256] = {
> I2bvIP(DstDI | SrcDX | Mov | String | Unaligned, em_in, ins, check_perm_in), /* insb, insw/insd */
> I2bvIP(SrcSI | DstDX | String, em_out, outs, check_perm_out), /* outsb, outsw/outsd */
[Severity: Medium]
Are we missing the NoRex2 flag for legacy string instructions like ins and
outs?
These instructions do not reference extended registers and must raise #UD when
prefixed with REX2, but they appear to lack the flag here. Without it, KVM's
emulator might diverge from hardware by successfully emulating instructions
that should raise #UD if a guest executes a REX2-prefixed legacy instruction
and forces emulation.
Similarly, it looks like cpuid in twobyte_table is also missing the NoRex2
flag.
> /* 0x70 - 0x7F */
> - X16(D(SrcImmByte | NearBranch | IsBranch)),
> + X16(D(SrcImmByte | NearBranch | IsBranch | NoRex2)),
> /* 0x80 - 0x87 */
[ ... ]
> @@ -4385,12 +4396,12 @@ static const struct opcode twobyte_table[256] = {
> N, GP(ModRM | DstMem | SrcReg | Mov | Sse | Avx, &pfx_0f_2b),
[Severity: Critical]
Does the lack of NoRex2 on legacy SSE instructions expose the host to a
kernel panic?
If a malicious guest executes a legacy SSE instruction (like movups) with a
REX2 prefix that specifies an extended register index, it could force
emulation. Since the NoRex2 flag is missing here, the emulator will
decode the instruction and extract an extended register index (>= 16)
via rex_get_rxb().
If this out-of-bounds index is passed to kvm_read_sse_reg():
arch/x86/kvm/emulate.c:__decode_register_operand() {
...
if (ctxt->d & (Avx|Sse)) {
...
kvm_read_sse_reg(reg, &op->vec_val);
...
}
It seems this would hit the default unhandled fallback in _kvm_read_sse_reg()
which triggers a BUG(), leading to a host denial of service:
arch/x86/kvm/fpu.h:_kvm_read_sse_reg() {
...
switch (reg) {
case 0: asm("movdqa %%xmm0, %0" : "=m"(*data)); break;
...
default: BUG();
}
}
> N, N, N, N,
> /* 0x30 - 0x3F */
> - II(ImplicitOps | Priv, em_wrmsr, wrmsr),
> - IIP(ImplicitOps, em_rdtsc, rdtsc, check_rdtsc),
> - 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),
> + II(ImplicitOps | Priv | NoRex2, em_wrmsr, wrmsr),
> + IIP(ImplicitOps | NoRex2, em_rdtsc, rdtsc, check_rdtsc),
> + II(ImplicitOps | Priv | NoRex2, em_rdmsr, rdmsr),
> + IIP(ImplicitOps | NoRex2, em_rdpmc, rdpmc, check_rdpmc),
> + I(ImplicitOps | EmulateOnUD | IsBranch | ShadowStack | NoRex2, em_sysenter),
> + I(ImplicitOps | Priv | EmulateOnUD | IsBranch | ShadowStack | NoRex2, em_sysexit),
> N, N,
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260902021804.718676-1-chang.seok.bae@intel.com?part=11
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v7 12/20] KVM: x86: Support REX2-prefixed opcode decode
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (10 preceding siblings ...)
2026-09-02 2:17 ` [PATCH v7 11/20] KVM: x86: Handle EGPR index and REX2-incompatible opcodes Chang S. Bae
@ 2026-09-02 2:17 ` Chang S. Bae
2026-09-02 2:17 ` [PATCH v7 13/20] KVM: x86: Reject EVEX-prefixed instructions Chang S. Bae
` (7 subsequent siblings)
19 siblings, 0 replies; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:17 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
Extend the instruction decoder to recognize and handle the REX2 prefix,
including validation of prefix sequences and correct opcode table
selection.
REX2 is a terminal prefix: once 0xD5 is encountered, the following byte
is the opcode. When REX.M=0, most prefix bytes are invalid after REX2,
including REX, VEX, EVEX, and another REX2. Also, REX2-prefixed
instructions are only valid in 64-bit mode.
All of the invalid prefix combinations after REX2 coincide with opcodes
that are architecturally invalid in 64-bit mode. Thus, marking such
opcodes with No64 in opcode_table[] naturally disallows those illegal
prefix sequences.
The 0x40–0x4F opcode row was missing the No64 flag. While NoRex2 already
invalidates REX2 for these opcodes, adding No64 makes opcode attributes
explicit and complete.
Suggested-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
Link: https://lore.kernel.org/CABgObfYYGTvkYpeyqLSr9JgKMDA_STSff2hXBNchLZuKFU+MMA@mail.gmail.com
---
arch/x86/kvm/emulate.c | 38 ++++++++++++++++++++++++++++++++++----
1 file changed, 34 insertions(+), 4 deletions(-)
diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c
index a1a567fd0378..eba4e9b7cd5c 100644
--- a/arch/x86/kvm/emulate.c
+++ b/arch/x86/kvm/emulate.c
@@ -4266,7 +4266,7 @@ static const struct opcode opcode_table[256] = {
/* 0x38 - 0x3F */
I6ALU(NoWrite, em_cmp), N, N,
/* 0x40 - 0x4F */
- X8(I(DstReg | NoRex2, em_inc)), X8(I(DstReg | NoRex2, em_dec)),
+ X8(I(DstReg | NoRex2 | No64, em_inc)), X8(I(DstReg | NoRex2 | No64, em_dec)),
/* 0x50 - 0x57 */
X8(I(SrcReg | Stack, em_push)),
/* 0x58 - 0x5F */
@@ -4859,6 +4859,17 @@ static int x86_decode_avx(struct x86_emulate_ctxt *ctxt,
return rc;
}
+static inline bool rex2_invalid(struct x86_emulate_ctxt *ctxt)
+{
+ const struct x86_emulate_ops *ops = ctxt->ops;
+ u64 xcr = 0;
+
+ return ctxt->rex_prefix == REX_PREFIX ||
+ !(ops->get_cr(ctxt, 4) & X86_CR4_OSXSAVE) ||
+ ops->get_xcr(ctxt, 0, &xcr) ||
+ !(xcr & XFEATURE_MASK_APX);
+}
+
int x86_decode_insn(struct x86_emulate_ctxt *ctxt, void *insn, int insn_len, int emulation_type)
{
int rc = X86EMUL_CONTINUE;
@@ -4912,7 +4923,7 @@ int x86_decode_insn(struct x86_emulate_ctxt *ctxt, void *insn, int insn_len, int
ctxt->op_bytes = def_op_bytes;
ctxt->ad_bytes = def_ad_bytes;
- /* Legacy prefixes. */
+ /* Legacy and REX/REX2 prefixes. */
for (;;) {
switch (ctxt->b = insn_fetch(u8, ctxt)) {
case 0x66: /* operand-size override */
@@ -4958,6 +4969,17 @@ int x86_decode_insn(struct x86_emulate_ctxt *ctxt, void *insn, int insn_len, int
ctxt->rex_prefix = REX_PREFIX;
ctxt->rex_bits = ctxt->b & 0xf;
continue;
+ case 0xd5: /* REX2 */
+ if (mode != X86EMUL_MODE_PROT64)
+ goto done_prefixes;
+ if (rex2_invalid(ctxt)) {
+ opcode = ud;
+ goto done_modrm;
+ }
+ ctxt->rex_prefix = REX2_PREFIX;
+ ctxt->rex_bits = insn_fetch(u8, ctxt);
+ ctxt->b = insn_fetch(u8, ctxt);
+ goto done_prefixes;
case 0xf0: /* LOCK */
ctxt->lock_prefix = 1;
break;
@@ -4980,6 +5002,12 @@ int x86_decode_insn(struct x86_emulate_ctxt *ctxt, void *insn, int insn_len, int
if (ctxt->rex_bits & REX_W)
ctxt->op_bytes = 8;
+ /* REX2 opcode is one byte unless M-bit selects the two-byte map */
+ if (ctxt->rex_bits & REX_M)
+ goto decode_twobytes;
+ else if (ctxt->rex_prefix == REX2_PREFIX)
+ goto decode_onebyte;
+
/* Opcode byte(s). */
if (ctxt->b == 0xc4 || ctxt->b == 0xc5) {
/* VEX or LDS/LES */
@@ -4997,17 +5025,19 @@ int x86_decode_insn(struct x86_emulate_ctxt *ctxt, void *insn, int insn_len, int
goto done;
} else if (ctxt->b == 0x0f) {
/* Two- or three-byte opcode */
- ctxt->opcode_len = 2;
ctxt->b = insn_fetch(u8, ctxt);
+decode_twobytes:
+ ctxt->opcode_len = 2;
opcode = twobyte_table[ctxt->b];
/* 0F_38 opcode map */
- if (ctxt->b == 0x38) {
+ if (ctxt->b == 0x38 && ctxt->rex_prefix != REX2_PREFIX) {
ctxt->opcode_len = 3;
ctxt->b = insn_fetch(u8, ctxt);
opcode = opcode_map_0f_38[ctxt->b];
}
} else {
+decode_onebyte:
/* Opcode byte(s). */
opcode = opcode_table[ctxt->b];
}
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v7 13/20] KVM: x86: Reject EVEX-prefixed instructions
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (11 preceding siblings ...)
2026-09-02 2:17 ` [PATCH v7 12/20] KVM: x86: Support REX2-prefixed opcode decode Chang S. Bae
@ 2026-09-02 2:17 ` Chang S. Bae
2026-09-02 2:59 ` sashiko-bot
2026-09-02 2:17 ` [PATCH v7 14/20] KVM: x86: Move KVM_SUPPORTED_{XCR0,XSS} into kvm_x86_vendor_init() Chang S. Bae
` (6 subsequent siblings)
19 siblings, 1 reply; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:17 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
Explicitly mark EVEX-prefixed opcodes (0x62) as unsupported.
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
arch/x86/kvm/emulate.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c
index eba4e9b7cd5c..f0d5a40eba27 100644
--- a/arch/x86/kvm/emulate.c
+++ b/arch/x86/kvm/emulate.c
@@ -5052,6 +5052,11 @@ int x86_decode_insn(struct x86_emulate_ctxt *ctxt, void *insn, int insn_len, int
if (opcode.flags & ModRM)
ctxt->modrm = insn_fetch(u8, ctxt);
+ /* EVEX-prefixed instructions are not implemented */
+ if (ctxt->opcode_len == 1 && ctxt->b == 0x62 &&
+ (mode == X86EMUL_MODE_PROT64 || (ctxt->modrm & 0xc0) == 0xc0))
+ opcode.flags = NotImpl;
+
done_modrm:
ctxt->d = opcode.flags;
while (ctxt->d & GroupMask) {
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v7 13/20] KVM: x86: Reject EVEX-prefixed instructions
2026-09-02 2:17 ` [PATCH v7 13/20] KVM: x86: Reject EVEX-prefixed instructions Chang S. Bae
@ 2026-09-02 2:59 ` sashiko-bot
0 siblings, 0 replies; 29+ messages in thread
From: sashiko-bot @ 2026-09-02 2:59 UTC (permalink / raw)
To: Chang S. Bae; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The EVEX prefix check reads `ctxt->modrm` without fetching it, resulting in the use of a stale value from a previously emulated instruction.
--
commit 01b255a08e34ec61090bf09c90c37d94d681cde4
Author: Chang S. Bae <chang.seok.bae@intel.com>
KVM: x86: Reject EVEX-prefixed instructions
Explicitly mark EVEX-prefixed opcodes (0x62) as unsupported.
> diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c
> index eba4e9b7cd5cf..f0d5a40eba27f 100644
> --- a/arch/x86/kvm/emulate.c
> +++ b/arch/x86/kvm/emulate.c
> @@ -5052,6 +5052,11 @@ int x86_decode_insn(struct x86_emulate_ctxt *ctxt, void *insn, int insn_len, int
> if (opcode.flags & ModRM)
> ctxt->modrm = insn_fetch(u8, ctxt);
>
> + /* EVEX-prefixed instructions are not implemented */
> + if (ctxt->opcode_len == 1 && ctxt->b == 0x62 &&
> + (mode == X86EMUL_MODE_PROT64 || (ctxt->modrm & 0xc0) == 0xc0))
> + opcode.flags = NotImpl;
> +
[Severity: Medium]
Does this EVEX prefix check read a stale ctxt->modrm value?
In x86_decode_insn(), opcode 0x62 is mapped to NotImpl in the 1-byte opcode
table, which lacks the ModRM flag. This means ctxt->modrm is not fetched
by the check right above this new block.
As a result, if a guest executes opcode 0x62 in 16-bit or 32-bit mode, this
condition evaluates (ctxt->modrm & 0xc0) == 0xc0 using a leftover value from
a previously emulated instruction.
It was previously suggested this might trigger KMSAN warnings and cause a
host denial of service. Since ctxt is allocated via kmem_cache_zalloc(),
KMSAN sees the memory as initialized, which makes the DoS concern inaccurate.
However, does this logic flaw still break the intended EVEX detection? Since
0x62 already evaluates to NotImpl during the opcode table lookup, it seems
the subsequent opcode.flags = NotImpl assignment here is a no-op with no
functional impact.
> done_modrm:
> ctxt->d = opcode.flags;
> while (ctxt->d & GroupMask) {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260902021804.718676-1-chang.seok.bae@intel.com?part=13
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v7 14/20] KVM: x86: Move KVM_SUPPORTED_{XCR0,XSS} into kvm_x86_vendor_init()
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (12 preceding siblings ...)
2026-09-02 2:17 ` [PATCH v7 13/20] KVM: x86: Reject EVEX-prefixed instructions Chang S. Bae
@ 2026-09-02 2:17 ` Chang S. Bae
2026-09-02 2:17 ` [PATCH v7 15/20] KVM: x86: Guard valid XCR0.APX settings Chang S. Bae
` (5 subsequent siblings)
19 siblings, 0 replies; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:17 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
From: Sean Christopherson <seanjc@google.com>
Move KVM_SUPPORTED_{XCR0,XSS} into kvm_x86_vendor_init() as "const u64"
values so that KVM's initialization code can further manipulate the set
of supported XCR0/XSS features without ugly #ifdeffery and without
risking other code treating KVM_SUPPORTED_{XCR0,XSS} as 100%
authoritative.
No functional change intended.
Signed-off-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
Link: https://lore.kernel.org/ahmtyMNsj2BCoEEo@google.com
---
arch/x86/kvm/cpuid.c | 2 +-
arch/x86/kvm/x86.c | 25 +++++++++++++------------
2 files changed, 14 insertions(+), 13 deletions(-)
diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
index a3f583a376ab..e72543088695 100644
--- a/arch/x86/kvm/cpuid.c
+++ b/arch/x86/kvm/cpuid.c
@@ -264,7 +264,7 @@ static u32 kvm_apply_cpuid_pv_features_quirk(struct kvm_vcpu *vcpu)
/*
* Calculate guest's supported XCR0 taking into account guest CPUID data and
- * KVM's supported XCR0 (comprised of host's XCR0 and KVM_SUPPORTED_XCR0).
+ * KVM's supported XCR0 (the host's actual XCR0 masked by what KVM supports).
*/
static u64 cpuid_get_supported_xcr0(struct kvm_vcpu *vcpu)
{
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 0df74422759f..91666b868ab9 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -208,19 +208,7 @@ module_param(pi_inject_timer, bint, 0644);
static bool __read_mostly mitigate_smt_rsb;
module_param(mitigate_smt_rsb, bool, 0444);
-#define KVM_SUPPORTED_XCR0 (XFEATURE_MASK_FP | XFEATURE_MASK_SSE \
- | XFEATURE_MASK_YMM | XFEATURE_MASK_BNDREGS \
- | XFEATURE_MASK_BNDCSR | XFEATURE_MASK_AVX512 \
- | XFEATURE_MASK_PKRU | XFEATURE_MASK_XTILE)
-
#define XFEATURE_MASK_CET_ALL (XFEATURE_MASK_CET_USER | XFEATURE_MASK_CET_KERNEL)
-/*
- * Note, KVM supports exposing PT to the guest, but does not support context
- * switching PT via XSTATE (KVM's PT virtualization relies on perf; swapping
- * PT via guest XSTATE would clobber perf state), i.e. KVM doesn't support
- * IA32_XSS[bit 8] (guests can/must use RDMSR/WRMSR to save/restore PT MSRs).
- */
-#define KVM_SUPPORTED_XSS (XFEATURE_MASK_CET_ALL)
bool __read_mostly allow_smaller_maxphyaddr = 0;
EXPORT_SYMBOL_FOR_KVM_INTERNAL(allow_smaller_maxphyaddr);
@@ -7059,6 +7047,19 @@ static void kvm_x86_check_cpu_compat(void *ret)
int kvm_x86_vendor_init(struct kvm_x86_init_ops *ops)
{
+ const u64 KVM_SUPPORTED_XCR0 = XFEATURE_MASK_FP | XFEATURE_MASK_SSE |
+ XFEATURE_MASK_YMM | XFEATURE_MASK_BNDREGS |
+ XFEATURE_MASK_BNDCSR | XFEATURE_MASK_AVX512 |
+ XFEATURE_MASK_PKRU | XFEATURE_MASK_XTILE;
+ /*
+ * Note, KVM supports exposing PT to the guest, but does not support
+ * context switching PT via XSTATE (KVM's PT virtualization relies on
+ * perf; swapping PT via guest XSTATE would clobber perf state), i.e.
+ * KVM doesn't support IA32_XSS[bit 8] (guests can/must use RDMSR/WRMSR
+ * to save/restore PT MSRs).
+ */
+ const u64 KVM_SUPPORTED_XSS = XFEATURE_MASK_CET_ALL;
+
u64 host_pat;
int r, cpu;
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v7 15/20] KVM: x86: Guard valid XCR0.APX settings
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (13 preceding siblings ...)
2026-09-02 2:17 ` [PATCH v7 14/20] KVM: x86: Move KVM_SUPPORTED_{XCR0,XSS} into kvm_x86_vendor_init() Chang S. Bae
@ 2026-09-02 2:17 ` Chang S. Bae
2026-09-02 2:18 ` [PATCH v7 16/20] KVM: x86: Add APX to supported XCR0 Chang S. Bae
` (4 subsequent siblings)
19 siblings, 0 replies; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:17 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
Prevent invalid XCR0.APX configurations in two cases: conflict with MPX
and lack of SVM support.
In the non-compacted XSAVE format, APX and MPX conflict on the same
offset. Although MPX is being deprecated in practice, KVM should
explicitly reject such configurations that set both bits.
At this point, only VMX supports EGPRs. SVM will require corresponding
extensions to handle EGPR indices.
Updating the supported XCR0 mask will be done separately.
Suggested-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
Link: https://lore.kernel.org/ab3f4937-38f5-4354-8850-bf773c159bbe@redhat.com
---
arch/x86/kvm/svm/svm.c | 7 ++++++-
arch/x86/kvm/x86.c | 4 ++++
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 7d59d301e1e5..2962501c4b67 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -5616,8 +5616,13 @@ static __init int svm_hardware_setup(void)
return -EOPNOTSUPP;
}
+ /*
+ * APX introduces EGPRs, which require additional VMCB support.
+ * Disable APX until the necessary extensions are handled.
+ */
kvm_caps.supported_xcr0 &= ~(XFEATURE_MASK_BNDREGS |
- XFEATURE_MASK_BNDCSR);
+ XFEATURE_MASK_BNDCSR |
+ XFEATURE_MASK_APX);
if (tsc_scaling) {
if (!boot_cpu_has(X86_FEATURE_TSCRATEMSR)) {
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 91666b868ab9..76f70d2631f6 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -699,6 +699,10 @@ int __kvm_set_xcr(struct kvm_vcpu *vcpu, u32 index, u64 xcr)
(!(xcr0 & XFEATURE_MASK_BNDCSR)))
return 1;
+ /* MPX and APX conflict in the non-compacted XSAVE format */
+ if (xcr0 & XFEATURE_MASK_BNDREGS && xcr0 & XFEATURE_MASK_APX)
+ return 1;
+
if (xcr0 & XFEATURE_MASK_AVX512) {
if (!(xcr0 & XFEATURE_MASK_YMM))
return 1;
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v7 16/20] KVM: x86: Add APX to supported XCR0
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (14 preceding siblings ...)
2026-09-02 2:17 ` [PATCH v7 15/20] KVM: x86: Guard valid XCR0.APX settings Chang S. Bae
@ 2026-09-02 2:18 ` Chang S. Bae
2026-09-02 3:07 ` sashiko-bot
2026-09-02 2:18 ` [PATCH v7 17/20] KVM: x86: Expose APX foundation feature to userspace Chang S. Bae
` (3 subsequent siblings)
19 siblings, 1 reply; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:18 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
Set kvm_caps.supported_xcr0[APX] when the host has enabled the APX state
along with CONFIG_KVM_APX=y.
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
arch/x86/kvm/x86.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 76f70d2631f6..8c6365122efb 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -7132,6 +7132,8 @@ int kvm_x86_vendor_init(struct kvm_x86_init_ops *ops)
if (boot_cpu_has(X86_FEATURE_XSAVE)) {
kvm_host.xcr0 = xgetbv(XCR_XFEATURE_ENABLED_MASK);
kvm_caps.supported_xcr0 = kvm_host.xcr0 & KVM_SUPPORTED_XCR0;
+ if (IS_ENABLED(CONFIG_KVM_APX))
+ kvm_caps.supported_xcr0 |= kvm_host.xcr0 & XFEATURE_MASK_APX;
}
if (boot_cpu_has(X86_FEATURE_XSAVES)) {
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v7 16/20] KVM: x86: Add APX to supported XCR0
2026-09-02 2:18 ` [PATCH v7 16/20] KVM: x86: Add APX to supported XCR0 Chang S. Bae
@ 2026-09-02 3:07 ` sashiko-bot
0 siblings, 0 replies; 29+ messages in thread
From: sashiko-bot @ 2026-09-02 3:07 UTC (permalink / raw)
To: Chang S. Bae; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Adding APX host support inflates KVM's FPU uabi_size beyond 4096 bytes for all VMs, completely breaking the KVM_GET_XSAVE and KVM_SET_XSAVE UAPIs for legacy VMMs on APX-capable hosts.
--
commit f809671f7be8f3b21dbbd90937f8c5a3e917c643
Author: Chang S. Bae <chang.seok.bae@intel.com>
KVM: x86: Add APX to supported XCR0
This commit sets kvm_caps.supported_xcr0 for APX when the host has enabled
the APX state and CONFIG_KVM_APX is configured. This allows KVM to expose
APX features to guests.
> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index 76f70d2631f67..8c6365122efb8 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
[ ... ]
> @@ -7132,6 +7132,8 @@ int kvm_x86_vendor_init(struct kvm_x86_init_ops *ops)
> if (boot_cpu_has(X86_FEATURE_XSAVE)) {
> kvm_host.xcr0 = xgetbv(XCR_XFEATURE_ENABLED_MASK);
> kvm_caps.supported_xcr0 = kvm_host.xcr0 & KVM_SUPPORTED_XCR0;
> + if (IS_ENABLED(CONFIG_KVM_APX))
> + kvm_caps.supported_xcr0 |= kvm_host.xcr0 & XFEATURE_MASK_APX;
[Severity: High]
This is a pre-existing issue, but does enabling APX host support here
inadvertently break the KVM_GET_XSAVE and KVM_SET_XSAVE UAPIs for legacy
VMMs?
Because APX (component 19) is a static user feature, its inclusion in
fpu_user_cfg.default_features requires an uncompacted offset that
accommodates the 8KB AMX (component 18) hole. This results in an uncompacted
offset greater than 10KB, which inflates fpu_user_cfg.default_size.
When KVM initializes a vCPU, it sets the baseline uabi_size to this
inflated value:
arch/x86/kernel/fpu/core.c:fpu_alloc_guest_fpstate() {
...
if (WARN_ON_ONCE(fpu_user_cfg.default_size > gfpu->uabi_size))
gfpu->uabi_size = fpu_user_cfg.default_size;
...
}
Consequently, uabi_size grows beyond the standard 4096 bytes for all VMs.
For older VMMs attempting to use the standard 4096-byte buffers,
kvm_arch_vcpu_ioctl() handles the UAPI boundary by unconditionally failing
KVM_GET_XSAVE with -EINVAL:
arch/x86/kvm/x86.c:kvm_arch_vcpu_ioctl() {
...
case KVM_GET_XSAVE: {
r = -EINVAL;
if (vcpu->arch.guest_fpu.uabi_size > sizeof(struct kvm_xsave))
break;
...
}
Similarly, for KVM_SET_XSAVE, it reads out of bounds from the 4096-byte user
buffer, returning -EFAULT:
arch/x86/kvm/x86.c:kvm_arch_vcpu_ioctl() {
...
case KVM_SET_XSAVE: {
int size = vcpu->arch.guest_fpu.uabi_size;
u.xsave = memdup_user(argp, size);
...
}
Could this deterministically break VM creation and live migration on
APX-capable hosts for legacy VMMs?
> }
>
> if (boot_cpu_has(X86_FEATURE_XSAVES)) {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260902021804.718676-1-chang.seok.bae@intel.com?part=16
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v7 17/20] KVM: x86: Expose APX foundation feature to userspace
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (15 preceding siblings ...)
2026-09-02 2:18 ` [PATCH v7 16/20] KVM: x86: Add APX to supported XCR0 Chang S. Bae
@ 2026-09-02 2:18 ` Chang S. Bae
2026-09-02 2:18 ` [PATCH v7 18/20] KVM: x86: Expose APX sub-features " Chang S. Bae
` (2 subsequent siblings)
19 siblings, 0 replies; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:18 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae, Peter Fang
Expose the APX feature to userspace component. Update the maximum
supported CPUID leaf to 0x29 to include the APX leaf.
On SVM, do not advertise APX, as EGPR support is not yet implemented.
No APX sub-features are enumerated yet. Those will be exposed in a
separate patch.
Originally-by: Peter Fang <peter.fang@intel.com>
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
arch/x86/include/asm/cpuid/api.h | 1 +
arch/x86/kvm/cpuid.c | 10 +++++++++-
arch/x86/kvm/svm/svm.c | 1 +
3 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/arch/x86/include/asm/cpuid/api.h b/arch/x86/include/asm/cpuid/api.h
index 82eddfa2347b..f38d5e8a3c05 100644
--- a/arch/x86/include/asm/cpuid/api.h
+++ b/arch/x86/include/asm/cpuid/api.h
@@ -182,6 +182,7 @@ static __always_inline bool cpuid_function_is_indexed(u32 function)
case 0x1e:
case 0x1f:
case 0x24:
+ case 0x29:
case 0x8000001d:
return true;
}
diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
index e72543088695..990ad7ea4818 100644
--- a/arch/x86/kvm/cpuid.c
+++ b/arch/x86/kvm/cpuid.c
@@ -1062,8 +1062,12 @@ void kvm_initialize_cpu_caps(void)
F(AVX_VNNI_INT16),
F(PREFETCHITI),
F(AVX10),
+ SCATTERED_F(APX),
);
+ if (!IS_ENABLED(CONFIG_KVM_APX))
+ kvm_cpu_cap_clear(X86_FEATURE_APX);
+
kvm_cpu_cap_init(CPUID_7_2_EDX,
F(INTEL_PSFD),
F(IPRED_CTRL),
@@ -1456,7 +1460,7 @@ static inline int __do_cpuid_func(struct kvm_cpuid_array *array, u32 function)
switch (function) {
case 0:
/* Limited to the highest leaf implemented in KVM. */
- entry->eax = min(entry->eax, 0x24U);
+ entry->eax = min(entry->eax, 0x29U);
break;
case 1:
cpuid_entry_override(entry, CPUID_1_EDX);
@@ -1727,6 +1731,10 @@ static inline int __do_cpuid_func(struct kvm_cpuid_array *array, u32 function)
}
break;
}
+ /* APX sub-features */
+ case 0x29:
+ entry->eax = entry->ebx = entry->ecx = entry->edx = 0;
+ break;
case KVM_CPUID_SIGNATURE: {
const u32 *sigptr = (const u32 *)KVM_SIGNATURE;
entry->eax = KVM_CPUID_FEATURES;
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 2962501c4b67..7d9314645872 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -5597,6 +5597,7 @@ static __init void svm_set_cpu_caps(void)
*/
kvm_cpu_cap_clear(X86_FEATURE_BUS_LOCK_DETECT);
kvm_cpu_cap_clear(X86_FEATURE_MSR_IMM);
+ kvm_cpu_cap_clear(X86_FEATURE_APX);
kvm_setup_xss_caps();
kvm_finalize_cpu_caps();
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v7 18/20] KVM: x86: Expose APX sub-features to userspace
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (16 preceding siblings ...)
2026-09-02 2:18 ` [PATCH v7 17/20] KVM: x86: Expose APX foundation feature to userspace Chang S. Bae
@ 2026-09-02 2:18 ` Chang S. Bae
2026-09-02 3:00 ` sashiko-bot
2026-09-02 2:18 ` [PATCH v7 19/20] KVM: x86: selftests: Add APX state and ABI test Chang S. Bae
2026-09-02 2:18 ` [PATCH v7 20/20] KVM: x86: selftests: Add APX state handling and XCR0 sanity checks Chang S. Bae
19 siblings, 1 reply; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:18 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
Add CPUID leaf 0x29 sub-leaf 0 to expose these APX sub-features, if the
APX foundation is available:
* New Conditional Instructions (NCI)
* New Data Destination (NDD)
* Flags Suppression (NF)
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
arch/x86/include/asm/kvm_host.h | 1 +
arch/x86/kvm/cpuid.c | 7 ++++++-
arch/x86/kvm/reverse_cpuid.h | 4 ++++
3 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 8a0de8b847ea..7da85bea0076 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -728,6 +728,7 @@ enum kvm_only_cpuid_leafs {
CPUID_7_1_ECX,
CPUID_1E_1_EAX,
CPUID_24_1_ECX,
+ CPUID_29_0_EBX,
NR_KVM_CPU_CAPS,
NKVMCAPINTS = NR_KVM_CPU_CAPS - NCAPINTS,
diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
index 990ad7ea4818..6c47e0ffedd8 100644
--- a/arch/x86/kvm/cpuid.c
+++ b/arch/x86/kvm/cpuid.c
@@ -1112,6 +1112,10 @@ void kvm_initialize_cpu_caps(void)
F(AVX10_VNNI_INT),
);
+ kvm_cpu_cap_init(CPUID_29_0_EBX,
+ F(APX_NCI_NDD_NF),
+ );
+
kvm_cpu_cap_init(CPUID_8000_0001_ECX,
F(LAHF_LM),
F(CMP_LEGACY),
@@ -1733,7 +1737,8 @@ static inline int __do_cpuid_func(struct kvm_cpuid_array *array, u32 function)
}
/* APX sub-features */
case 0x29:
- entry->eax = entry->ebx = entry->ecx = entry->edx = 0;
+ if (!kvm_cpu_cap_has(X86_FEATURE_APX))
+ entry->eax = entry->ebx = entry->ecx = entry->edx = 0;
break;
case KVM_CPUID_SIGNATURE: {
const u32 *sigptr = (const u32 *)KVM_SIGNATURE;
diff --git a/arch/x86/kvm/reverse_cpuid.h b/arch/x86/kvm/reverse_cpuid.h
index 80b41d5559da..b20c8be4fea6 100644
--- a/arch/x86/kvm/reverse_cpuid.h
+++ b/arch/x86/kvm/reverse_cpuid.h
@@ -69,6 +69,9 @@
/* Intel-defined sub-features, CPUID level 0x00000024:1 (ECX) */
#define X86_FEATURE_AVX10_VNNI_INT KVM_X86_FEATURE(CPUID_24_1_ECX, 2)
+/* Intel-defined sub-features, CPUID level 0x00000029:0 (EBX) */
+#define X86_FEATURE_APX_NCI_NDD_NF KVM_X86_FEATURE(CPUID_29_0_EBX, 0)
+
/* CPUID level 0x80000007 (EDX). */
#define KVM_X86_FEATURE_CONSTANT_TSC KVM_X86_FEATURE(CPUID_8000_0007_EDX, 8)
@@ -112,6 +115,7 @@ static const struct cpuid_reg reverse_cpuid[] = {
[CPUID_7_1_ECX] = { 7, 1, CPUID_ECX},
[CPUID_1E_1_EAX] = { 0x1e, 1, CPUID_EAX},
[CPUID_24_1_ECX] = { 0x24, 1, CPUID_ECX},
+ [CPUID_29_0_EBX] = { 0x29, 0, CPUID_EBX},
};
/*
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v7 18/20] KVM: x86: Expose APX sub-features to userspace
2026-09-02 2:18 ` [PATCH v7 18/20] KVM: x86: Expose APX sub-features " Chang S. Bae
@ 2026-09-02 3:00 ` sashiko-bot
0 siblings, 0 replies; 29+ messages in thread
From: sashiko-bot @ 2026-09-02 3:00 UTC (permalink / raw)
To: Chang S. Bae; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] KVM exposes unmasked host APX sub-features and leaks raw host CPUID state to the guest by failing to apply the capability mask for leaf 0x29.
--
commit c301f6e3ae94455385fa1906a5a873f104314430
Author: Chang S. Bae <chang.seok.bae@intel.com>
KVM: x86: Expose APX sub-features to userspace
Add CPUID leaf 0x29 sub-leaf 0 to expose these APX sub-features, if the
APX foundation is available: New Conditional Instructions (NCI), New Data
Destination (NDD), and Flags Suppression (NF).
> diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
> index 990ad7ea48186..6c47e0ffedd84 100644
> --- a/arch/x86/kvm/cpuid.c
> +++ b/arch/x86/kvm/cpuid.c
[ ... ]
> @@ -1733,7 +1737,8 @@ static inline int __do_cpuid_func(struct kvm_cpuid_array *array, u32 function)
> }
> /* APX sub-features */
> case 0x29:
> - entry->eax = entry->ebx = entry->ecx = entry->edx = 0;
> + if (!kvm_cpu_cap_has(X86_FEATURE_APX))
> + entry->eax = entry->ebx = entry->ecx = entry->edx = 0;
> break;
[Severity: Medium]
Will this leak unmasked raw host CPUID state to the guest when APX is
available?
Since do_host_cpuid() populates the initial entry directly from the physical
CPU's output, it appears this case completes without calling
cpuid_entry_override(entry, CPUID_29_0_EBX) to apply the capability mask.
Does omitting the override mean KVM passes the raw host CPUID 0x29:0 directly
to the guest, including eax, ecx, edx, and any unsupported ebx bits?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260902021804.718676-1-chang.seok.bae@intel.com?part=18
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v7 19/20] KVM: x86: selftests: Add APX state and ABI test
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (17 preceding siblings ...)
2026-09-02 2:18 ` [PATCH v7 18/20] KVM: x86: Expose APX sub-features " Chang S. Bae
@ 2026-09-02 2:18 ` Chang S. Bae
2026-09-02 3:07 ` sashiko-bot
2026-09-02 2:18 ` [PATCH v7 20/20] KVM: x86: selftests: Add APX state handling and XCR0 sanity checks Chang S. Bae
19 siblings, 1 reply; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:18 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
Test APX-specific behavior and ABI interactions as these are unique
comparing to other state components:
* EGPR state is saved on VM entry assembly (unlike other components).
* The saved state is retained even if the guest disables APX.
* EGPR state is exposed via the XSAVE ABI while not stored in the
kernel XSAVE buffer.
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../selftests/kvm/include/x86/processor.h | 120 +++++++++++
tools/testing/selftests/kvm/x86/apx_test.c | 191 ++++++++++++++++++
3 files changed, 312 insertions(+)
create mode 100644 tools/testing/selftests/kvm/x86/apx_test.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 96bab7002d39..2bd5c77047a8 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -166,6 +166,7 @@ TEST_GEN_PROGS_x86 += rseq_test
TEST_GEN_PROGS_x86 += steal_time
TEST_GEN_PROGS_x86 += system_counter_offset_test
TEST_GEN_PROGS_x86 += pre_fault_memory_test
+TEST_GEN_PROGS_x86 += x86/apx_test
# Compiled outputs used by test targets
TEST_GEN_PROGS_EXTENDED_x86 += x86/nx_huge_pages_test
diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h
index 6e6f70035508..6e3e7d96c571 100644
--- a/tools/testing/selftests/kvm/include/x86/processor.h
+++ b/tools/testing/selftests/kvm/include/x86/processor.h
@@ -108,6 +108,7 @@ struct xstate {
#define XFEATURE_MASK_LBR BIT_ULL(15)
#define XFEATURE_MASK_XTILE_CFG BIT_ULL(17)
#define XFEATURE_MASK_XTILE_DATA BIT_ULL(18)
+#define XFEATURE_MASK_APX BIT_ULL(19)
#define XFEATURE_MASK_AVX512 (XFEATURE_MASK_OPMASK | \
XFEATURE_MASK_ZMM_Hi256 | \
@@ -195,6 +196,7 @@ struct kvm_x86_cpu_feature {
#define X86_FEATURE_SPEC_CTRL KVM_X86_CPU_FEATURE(0x7, 0, EDX, 26)
#define X86_FEATURE_ARCH_CAPABILITIES KVM_X86_CPU_FEATURE(0x7, 0, EDX, 29)
#define X86_FEATURE_PKS KVM_X86_CPU_FEATURE(0x7, 0, ECX, 31)
+#define X86_FEATURE_APX KVM_X86_CPU_FEATURE(0x7, 1, EDX, 21)
#define X86_FEATURE_XTILECFG KVM_X86_CPU_FEATURE(0xD, 0, EAX, 17)
#define X86_FEATURE_XTILEDATA KVM_X86_CPU_FEATURE(0xD, 0, EAX, 18)
#define X86_FEATURE_XSAVES KVM_X86_CPU_FEATURE(0xD, 1, EAX, 3)
@@ -917,6 +919,124 @@ static inline void write_sse_reg(int reg, const sse128_t *data)
}
}
+static inline unsigned long read_egpr(int reg)
+{
+ unsigned long data = 0;
+
+ /* mov %r16..%r31, %rax */
+ switch (reg) {
+ case 16:
+ asm(".byte 0xd5, 0x48, 0x89, 0xc0" : "=a"(data));
+ break;
+ case 17:
+ asm(".byte 0xd5, 0x48, 0x89, 0xc8" : "=a"(data));
+ break;
+ case 18:
+ asm(".byte 0xd5, 0x48, 0x89, 0xd0" : "=a"(data));
+ break;
+ case 19:
+ asm(".byte 0xd5, 0x48, 0x89, 0xd8" : "=a"(data));
+ break;
+ case 20:
+ asm(".byte 0xd5, 0x48, 0x89, 0xe0" : "=a"(data));
+ break;
+ case 21:
+ asm(".byte 0xd5, 0x48, 0x89, 0xe8" : "=a"(data));
+ break;
+ case 22:
+ asm(".byte 0xd5, 0x48, 0x89, 0xf0" : "=a"(data));
+ break;
+ case 23:
+ asm(".byte 0xd5, 0x48, 0x89, 0xf8" : "=a"(data));
+ break;
+ case 24:
+ asm(".byte 0xd5, 0x4c, 0x89, 0xc0" : "=a"(data));
+ break;
+ case 25:
+ asm(".byte 0xd5, 0x4c, 0x89, 0xc8" : "=a"(data));
+ break;
+ case 26:
+ asm(".byte 0xd5, 0x4c, 0x89, 0xd0" : "=a"(data));
+ break;
+ case 27:
+ asm(".byte 0xd5, 0x4c, 0x89, 0xd8" : "=a"(data));
+ break;
+ case 28:
+ asm(".byte 0xd5, 0x4c, 0x89, 0xe0" : "=a"(data));
+ break;
+ case 29:
+ asm(".byte 0xd5, 0x4c, 0x89, 0xe8" : "=a"(data));
+ break;
+ case 30:
+ asm(".byte 0xd5, 0x4c, 0x89, 0xf0" : "=a"(data));
+ break;
+ case 31:
+ asm(".byte 0xd5, 0x4c, 0x89, 0xf8" : "=a"(data));
+ break;
+ default:
+ BUG();
+ }
+
+ return data;
+}
+
+static inline void write_egpr(int reg, unsigned long data)
+{
+ /* mov %%rax, %r16...%r31*/
+ switch (reg) {
+ case 16:
+ asm(".byte 0xd5, 0x18, 0x89, 0xc0" : : "a"(data));
+ break;
+ case 17:
+ asm(".byte 0xd5, 0x18, 0x89, 0xc1" : : "a"(data));
+ break;
+ case 18:
+ asm(".byte 0xd5, 0x18, 0x89, 0xc2" : : "a"(data));
+ break;
+ case 19:
+ asm(".byte 0xd5, 0x18, 0x89, 0xc3" : : "a"(data));
+ break;
+ case 20:
+ asm(".byte 0xd5, 0x18, 0x89, 0xc4" : : "a"(data));
+ break;
+ case 21:
+ asm(".byte 0xd5, 0x18, 0x89, 0xc5" : : "a"(data));
+ break;
+ case 22:
+ asm(".byte 0xd5, 0x18, 0x89, 0xc6" : : "a"(data));
+ break;
+ case 23:
+ asm(".byte 0xd5, 0x18, 0x89, 0xc7" : : "a"(data));
+ break;
+ case 24:
+ asm(".byte 0xd5, 0x19, 0x89, 0xc0" : : "a"(data));
+ break;
+ case 25:
+ asm(".byte 0xd5, 0x19, 0x89, 0xc1" : : "a"(data));
+ break;
+ case 26:
+ asm(".byte 0xd5, 0x19, 0x89, 0xc2" : : "a"(data));
+ break;
+ case 27:
+ asm(".byte 0xd5, 0x19, 0x89, 0xc3" : : "a"(data));
+ break;
+ case 28:
+ asm(".byte 0xd5, 0x19, 0x89, 0xc4" : : "a"(data));
+ break;
+ case 29:
+ asm(".byte 0xd5, 0x19, 0x89, 0xc5" : : "a"(data));
+ break;
+ case 30:
+ asm(".byte 0xd5, 0x19, 0x89, 0xc6" : : "a"(data));
+ break;
+ case 31:
+ asm(".byte 0xd5, 0x19, 0x89, 0xc7" : : "a"(data));
+ break;
+ default:
+ BUG();
+ }
+}
+
static inline void invlpg(u64 addr)
{
__asm__ __volatile__("invlpg (%0)" : : "r"(addr) : "memory");
diff --git a/tools/testing/selftests/kvm/x86/apx_test.c b/tools/testing/selftests/kvm/x86/apx_test.c
new file mode 100644
index 000000000000..4825003d0616
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/apx_test.c
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "processor.h"
+
+enum stages {
+ GUEST_UPDATE,
+ USERSPACE_UPDATE,
+ GUEST_APXOFF,
+};
+
+enum egpr_ops {
+ EGPRS_WRITE,
+ EGPRS_CHECK,
+};
+
+#define for_each_egpr(reg) for (reg = 16; reg <= 31; reg++)
+
+/*
+ * Deterministic per-stage test values for EGPRs so that guest and
+ * userspace can validate state transitions.
+ */
+static inline unsigned long egpr_data(enum stages stage, int reg)
+{
+ switch (stage) {
+ case GUEST_UPDATE:
+ return 0xabcd + reg;
+ case USERSPACE_UPDATE:
+ return 0xbcde + reg;
+ case GUEST_APXOFF:
+ return 0xcdef + reg;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * Read/write or validate EGPR values either directly via registers
+ * (guest context) or via a provided buffer (userspace XSAVE).
+ */
+static bool handle_egprs(enum egpr_ops ops, unsigned long *egprs, enum stages stage)
+{
+ unsigned long data;
+ int reg;
+
+ for_each_egpr(reg) {
+ data = egpr_data(stage, reg);
+
+ if (ops == EGPRS_WRITE) {
+ if (egprs)
+ egprs[reg - 16] = data;
+ else
+ write_egpr(reg, data);
+ continue;
+ }
+
+ if (ops != EGPRS_CHECK)
+ return false;
+
+ if (egprs) {
+ if (egprs[reg - 16] != data)
+ return false;
+ continue;
+ }
+
+ if (read_egpr(reg) != data)
+ return false;
+ }
+
+ return true;
+}
+
+static void write_egprs(enum stages stage)
+{
+ handle_egprs(EGPRS_WRITE, NULL, stage);
+}
+
+static bool validate_egprs(enum stages stage)
+{
+ return handle_egprs(EGPRS_CHECK, NULL, stage);
+}
+
+static void test_guest_update(void)
+{
+ write_egprs(GUEST_UPDATE);
+ GUEST_SYNC(GUEST_UPDATE);
+ GUEST_ASSERT(validate_egprs(GUEST_UPDATE));
+}
+
+static void test_userspace_update(void)
+{
+ /* Userspace updates EGPR state via the KVM XSAVE ABI */
+ GUEST_SYNC(USERSPACE_UPDATE);
+ GUEST_ASSERT(validate_egprs(USERSPACE_UPDATE));
+}
+
+static void test_guest_apxoff(void)
+{
+ write_egprs(GUEST_APXOFF);
+ /* Disable APX to verify state is preserved */
+ GUEST_ASSERT(!xsetbv_safe(0, this_cpu_supported_xcr0() & ~XFEATURE_MASK_APX));
+ GUEST_SYNC(GUEST_APXOFF);
+ GUEST_ASSERT(!xsetbv_safe(0, this_cpu_supported_xcr0()));
+ GUEST_ASSERT(validate_egprs(GUEST_APXOFF));
+}
+
+static void guest_code(void)
+{
+ set_cr4(get_cr4() | X86_CR4_OSXSAVE);
+ GUEST_ASSERT(!xsetbv_safe(0, this_cpu_supported_xcr0()));
+
+ test_guest_update();
+ test_userspace_update();
+ test_guest_apxoff();
+
+ GUEST_DONE();
+}
+
+#define X86_PROPERTY_XSTATE_APX_OFFSET KVM_X86_CPU_PROPERTY(0xd, 19, EBX, 0, 31)
+#define XSAVE_HDR_OFFSET 512
+
+static inline unsigned long *xsave_egprs(void *xsave)
+{
+ return xsave + kvm_cpu_property(X86_PROPERTY_XSTATE_APX_OFFSET);
+}
+
+static inline void xstatebv_set(void *xsave, uint64_t mask)
+{
+ *(uint64_t *)(xsave + XSAVE_HDR_OFFSET) |= mask;
+}
+
+static void write_xsave_egprs(void *xsave, enum stages stage)
+{
+ handle_egprs(EGPRS_WRITE, xsave_egprs(xsave), stage);
+ xstatebv_set(xsave, XFEATURE_MASK_APX);
+}
+
+static bool validate_xsave_egprs(void *xsave, enum stages stage)
+{
+ return handle_egprs(EGPRS_CHECK, xsave_egprs(xsave), stage);
+}
+
+int main(int argc, char *argv[])
+{
+ struct kvm_xsave *xsave;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ enum stages stage;
+ struct ucall uc;
+ int xsave_size;
+
+ TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_XSAVE));
+ TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_APX));
+
+ vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+ xsave_size = vm_check_cap(vcpu->vm, KVM_CAP_XSAVE2);
+ TEST_ASSERT(xsave_size, "KVM_CAP_XSAVE2 not supported");
+ xsave = malloc(xsave_size);
+ TEST_ASSERT(xsave, "Failed to allocate XSAVE buffer");
+
+ while (1) {
+ vcpu_run(vcpu);
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ case UCALL_SYNC: {
+ stage = uc.args[1];
+ vcpu_xsave2_get(vcpu, xsave);
+ if (stage == USERSPACE_UPDATE) {
+ write_xsave_egprs(xsave, stage);
+ } else {
+ TEST_ASSERT(validate_xsave_egprs(xsave, stage),
+ "EGPR state mismatch in userspace XSAVE buffer");
+ }
+ vcpu_xsave_set(vcpu, xsave);
+ break;
+ }
+ case UCALL_DONE:
+ goto done;
+ default:
+ TEST_FAIL("Unknown ucall %lu", uc.cmd);
+ }
+ }
+
+done:
+ free(xsave);
+ kvm_vm_free(vm);
+ return 0;
+}
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v7 19/20] KVM: x86: selftests: Add APX state and ABI test
2026-09-02 2:18 ` [PATCH v7 19/20] KVM: x86: selftests: Add APX state and ABI test Chang S. Bae
@ 2026-09-02 3:07 ` sashiko-bot
0 siblings, 0 replies; 29+ messages in thread
From: sashiko-bot @ 2026-09-02 3:07 UTC (permalink / raw)
To: Chang S. Bae; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The inline assembly in `read_egpr()` lacks the `volatile` qualifier, allowing the compiler to optimize or hoist the register reads incorrectly.
--
commit 95fe69711afd71a902fad394278581f460dbab32
Author: Chang S. Bae <chang.seok.bae@intel.com>
KVM: x86: selftests: Add APX state and ABI test
This commit adds testing for APX-specific behavior and ABI interactions.
It verifies that EGPR state is correctly saved and retained across VM entry,
and tests its exposure via the XSAVE ABI.
> diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h
> index 6e6f70035508a..6e3e7d96c571e 100644
> --- a/tools/testing/selftests/kvm/include/x86/processor.h
> +++ b/tools/testing/selftests/kvm/include/x86/processor.h
[ ... ]
> @@ -917,6 +919,124 @@ static inline void write_sse_reg(int reg, const sse128_t *data)
> }
> }
>
> +static inline unsigned long read_egpr(int reg)
> +{
> + unsigned long data = 0;
> +
> + /* mov %r16..%r31, %rax */
> + switch (reg) {
> + case 16:
> + asm(".byte 0xd5, 0x48, 0x89, 0xc0" : "=a"(data));
[Severity: Medium]
Is it possible for the compiler to hoist these register reads outside of
their intended execution points?
According to GCC and Clang semantics, an asm statement with output operands
but no input operands and no volatile qualifier is treated as a pure
computation.
Could the optimizer move these reads to execute before the GUEST_SYNC()
calls, causing the test to validate stale register states (from before the
VM exit) instead of the intended restored states upon VM entry?
Adding the volatile qualifier would prevent the compiler from reordering
these hardware register reads.
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260902021804.718676-1-chang.seok.bae@intel.com?part=19
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v7 20/20] KVM: x86: selftests: Add APX state handling and XCR0 sanity checks
2026-09-02 2:17 [PATCH v7 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (18 preceding siblings ...)
2026-09-02 2:18 ` [PATCH v7 19/20] KVM: x86: selftests: Add APX state and ABI test Chang S. Bae
@ 2026-09-02 2:18 ` Chang S. Bae
19 siblings, 0 replies; 29+ messages in thread
From: Chang S. Bae @ 2026-09-02 2:18 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
Now that KVM exposes the APX feature to guests, extend some existing
selftests to validate XCR0 configuration and state management.
Since APX repurposes the XSAVE area previously used by MPX in the
non-compacted format, add a check to ensure that MPX state is not set
when APX is enabled.
Also, load non-init APX state data in the guest so that XSTATE_BV[APX] is
set, allowing validation of APX state testing.
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
tools/testing/selftests/kvm/x86/state_test.c | 3 +++
.../selftests/kvm/x86/xcr0_cpuid_test.c | 19 +++++++++++++++++++
2 files changed, 22 insertions(+)
diff --git a/tools/testing/selftests/kvm/x86/state_test.c b/tools/testing/selftests/kvm/x86/state_test.c
index 4a1056a6cb8d..9727dbb2b981 100644
--- a/tools/testing/selftests/kvm/x86/state_test.c
+++ b/tools/testing/selftests/kvm/x86/state_test.c
@@ -164,6 +164,9 @@ static void __attribute__((__flatten__)) guest_code(void *arg)
asm volatile ("vmovupd %0, %%zmm16" :: "m" (buffer));
}
+ if (supported_xcr0 & XFEATURE_MASK_APX)
+ write_egpr(16, 0xcccccccc);
+
if (this_cpu_has(X86_FEATURE_MPX)) {
u64 bounds[2] = { 10, 0xffffffffull };
u64 output[2] = { };
diff --git a/tools/testing/selftests/kvm/x86/xcr0_cpuid_test.c b/tools/testing/selftests/kvm/x86/xcr0_cpuid_test.c
index 40dc9e6b3fad..f74978ef5951 100644
--- a/tools/testing/selftests/kvm/x86/xcr0_cpuid_test.c
+++ b/tools/testing/selftests/kvm/x86/xcr0_cpuid_test.c
@@ -46,6 +46,20 @@ do { \
__supported, (xfeatures)); \
} while (0)
+/*
+ * Verify that mutually exclusive architectural features do not overlap.
+ * For example, APX and MPX must never be reported as supported together.
+ */
+#define ASSERT_XFEATURE_CONFLICT(supported_xcr0, xfeatures, conflicts) \
+do { \
+ uint64_t __supported = (supported_xcr0) & ((xfeatures) | (conflicts)); \
+ \
+ __GUEST_ASSERT((__supported & (xfeatures)) != (xfeatures) || \
+ !(__supported & (conflicts)), \
+ "supported = 0x%lx, xfeatures = 0x%llx, conflicts = 0x%llx", \
+ __supported, (xfeatures), (conflicts)); \
+} while (0)
+
static void guest_code(void)
{
u64 initial_xcr0;
@@ -79,6 +93,11 @@ static void guest_code(void)
ASSERT_ALL_OR_NONE_XFEATURE(supported_xcr0,
XFEATURE_MASK_XTILE);
+ /* Check APX by ensuring MPX is not exposed concurrently */
+ ASSERT_XFEATURE_CONFLICT(supported_xcr0,
+ XFEATURE_MASK_APX,
+ XFEATURE_MASK_BNDREGS | XFEATURE_MASK_BNDCSR);
+
vector = xsetbv_safe(0, XFEATURE_MASK_FP);
__GUEST_ASSERT(!vector,
"Expected success on XSETBV(FP), got %s",
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread