* [PATCH v5 00/20] KVM: x86: Enable APX for guests
@ 2026-07-20 17:19 Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 01/20] KVM: x86: Extend VCPU registers for EGPRs Chang S. Bae
` (19 more replies)
0 siblings, 20 replies; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
Hi Paolo and Sean,
Here is another iteration for this enabling series.
One topic worth highlighting first is about how to handle userspace
writes to the APX state through the KVM XSAVE API, following the
discussion here:
https://lore.kernel.org/CABgObfaocwfLdUBKJYYYEDH8X3n6iEbMsscbaCe_C=8JNnhDBA@mail.gmail.com
So what this revision ended up with is just to keep those copy paths from
userspace and acknowledge the redundant copy to VCPU cache / guest
fpstate. Patch3 has more context on that.
Besides that, this revision has several fixes and improvements. Sean
gave feedbacks. 0-day spotted on an issue. Also, since the preparatory
patches have been merged into the mainline, finally a review bot could
provide some feedbacks as well. The major changes are:
1. VMX Instruction Information Extension
During the time of V4, 0-day reported out an issue with patch7.
Fixing that also revealed patch4 was missing another function.
With a review bot's feeback, patch7 was also updated to ensure EGPR
indices for handling debug-register accesses. Also looking at
associated macros, patch6 was added for further cleanup.
2. Emulator Register Tracking
The review bot at hand also found a potential shift-out-bounds issue
in the register tracking. Patch10 was fixed.
3. APX Feature Exposure
As per Sean's feedback, the APX XCR0 and userspace exposure were
reworked a bit in patch14-18.
Along with the in-tree KVM x86 selftests, this time I ensured running the
x86 KVM-unit-test comparing between:
* Baseline - Paolo's `queue` branch
* This series on non-APX systems, and
* This series on APX hardware
The full set is also available at:
git://github.com/intel/apx.git apx-kvm_v5
Thanks,
Chang
Relevant Posting
* V4: https://lore.kernel.org/20260512011502.53072-1-chang.seok.bae@intel.com
* KVM-unit-test patch: https://lore.kernel.org/20260420212355.507827-1-chang.seok.bae@intel.com
Chang S. Bae (19):
KVM: x86: Extend VCPU registers for EGPRs
KVM: VMX: Save guest EGPRs in VCPU cache
KVM: x86: Support APX state for XSAVE ABI
KVM: VMX: Refactor VMX instruction information access
KVM: VMX: Refactor instruction information decoding
KVM: VMX: Remove unused control-register access defines
KVM: VMX: Refactor register index retrieval from exit qualification
KVM: VMX: Support instruction information extension
KVM: nVMX: Propagate extended instruction information
KVM: x86: Support EGPR accessing and tracking for emulator
KVM: x86: Handle EGPR index and REX2-incompatible opcodes
KVM: x86: Support REX2-prefixed opcode decode
KVM: x86: Reject EVEX-prefixed instructions
KVM: x86: Guard valid XCR0.APX settings
KVM: x86: Add APX in supported XCR0
KVM: x86: Expose APX foundation feature to userspace
KVM: x86: Expose APX sub-features to userspace
KVM: x86: selftests: Add APX state and ABI test
KVM: x86: selftests: Add APX state handling and XCR0 sanity checks
Sean Christopherson (1):
KVM: x86: Move KVM_SUPPORTED_{XCR0,XSS} into kvm_x86_vendor_init()
arch/x86/Kconfig.assembler | 5 +
arch/x86/include/asm/kvm_host.h | 19 ++
arch/x86/include/asm/kvm_vcpu_regs.h | 50 +++++
arch/x86/include/asm/vmx.h | 22 +-
arch/x86/kvm/Kconfig | 4 +
arch/x86/kvm/cpuid.c | 27 ++-
arch/x86/kvm/cpuid.h | 2 +
arch/x86/kvm/emulate.c | 121 +++++++----
arch/x86/kvm/kvm_emulate.h | 21 +-
arch/x86/kvm/reverse_cpuid.h | 6 +
arch/x86/kvm/svm/svm.c | 8 +-
arch/x86/kvm/vmenter.h | 1 +
arch/x86/kvm/vmx/nested.c | 75 +++----
arch/x86/kvm/vmx/nested.h | 2 +-
arch/x86/kvm/vmx/vmcs12.c | 1 +
arch/x86/kvm/vmx/vmcs12.h | 3 +-
arch/x86/kvm/vmx/vmenter.S | 31 ++-
arch/x86/kvm/vmx/vmx.c | 28 ++-
arch/x86/kvm/vmx/vmx.h | 77 ++++++-
arch/x86/kvm/x86.c | 92 +++++++--
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../selftests/kvm/include/x86/processor.h | 120 +++++++++++
tools/testing/selftests/kvm/x86/apx_test.c | 191 ++++++++++++++++++
tools/testing/selftests/kvm/x86/state_test.c | 3 +
.../selftests/kvm/x86/xcr0_cpuid_test.c | 19 ++
25 files changed, 801 insertions(+), 128 deletions(-)
create mode 100644 tools/testing/selftests/kvm/x86/apx_test.c
base-commit: a204badd8432f93b7e862e7dac6db0fe3d65f370
--
2.53.0
^ permalink raw reply [flat|nested] 31+ messages in thread
* [PATCH v5 01/20] KVM: x86: Extend VCPU registers for EGPRs
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 02/20] KVM: VMX: Save guest EGPRs in VCPU cache Chang S. Bae
` (18 subsequent siblings)
19 siblings, 0 replies; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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 b517257a6315..48547842d9fa 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -178,6 +178,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 801bf9e520db..f27e3f2937f0 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] 31+ messages in thread
* [PATCH v5 02/20] KVM: VMX: Save guest EGPRs in VCPU cache
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 01/20] KVM: x86: Extend VCPU registers for EGPRs Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 18:20 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 03/20] KVM: x86: Support APX state for XSAVE ABI Chang S. Bae
` (17 subsequent siblings)
19 siblings, 1 reply; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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 restore 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
---
V4 -> V5:
* Massage the chagnelog (Paolo [*])
* Fix typo; use KVM_ENTER_EGPR_SWITCH consistently
[*]: Paolo's rewording appeared when it was once picked into his queue branch
---
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..3b41ec89468d 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 >= V18
+
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 f27e3f2937f0..eb71cd7e5b37 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..702d86ade6b1 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, simply saving EGPRs always.
+ */
+ 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 3681d565f177..1c86919d14d4 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -991,6 +991,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] 31+ messages in thread
* [PATCH v5 03/20] KVM: x86: Support APX state for XSAVE ABI
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 01/20] KVM: x86: Extend VCPU registers for EGPRs Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 02/20] KVM: VMX: Save guest EGPRs in VCPU cache Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 18:22 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 04/20] KVM: VMX: Refactor VMX instruction information access Chang S. Bae
` (16 subsequent siblings)
19 siblings, 1 reply; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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 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 it simple although sub-optimal.
[1] Except for PKRU state, as stored in struct thread_struct.
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
V4 -> V5: Copy to both storages
The optimization bit had been reconsidered a little. V4 was attempting to
introduce the one-off exception into the generic copy function. Before
that, V3 had the tweak at the KVM site under the APX copy function.
But unless either approach could reach some agreeable shape, the tweak
appears to remain mere optimization. Here the third option would be
simply absorbing the duplication, which isn't significant in this slow
path in practice. Making simple looks to be a overall win, otherwise.
---
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 2698fa42cd97..122e46db9a15 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 0626e835e9eb..5578df10179a 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -3124,6 +3124,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)
{
@@ -3145,8 +3188,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;
}
@@ -3161,6 +3211,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;
@@ -3172,6 +3223,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] 31+ messages in thread
* [PATCH v5 04/20] KVM: VMX: Refactor VMX instruction information access
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (2 preceding siblings ...)
2026-07-20 17:19 ` [PATCH v5 03/20] KVM: x86: Support APX state for XSAVE ABI Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 05/20] KVM: VMX: Refactor instruction information decoding Chang S. Bae
` (15 subsequent siblings)
19 siblings, 0 replies; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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>
---
V4 -> V5: Convert vmx_get_msr_imm_reg() and keep `u32 type` in handle_invept()
---
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 0635e92471c8..92b530baa0ee 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -5234,7 +5234,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;
@@ -5366,7 +5366,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;
@@ -5651,7 +5651,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;
@@ -5757,7 +5757,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;
@@ -5946,7 +5946,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;
@@ -5974,8 +5974,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;
@@ -5983,6 +5983,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) ||
@@ -5994,7 +5995,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);
@@ -6054,7 +6055,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;
@@ -6075,7 +6076,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);
@@ -6428,7 +6429,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;
@@ -6436,7 +6437,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 6d6cd5904ddf..fb4f1757cb40 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 1c86919d14d4..8a4b578252c2 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -6158,7 +6158,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 {
@@ -6172,7 +6172,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);
@@ -6323,7 +6323,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 de9de0d2016c..a3f58a3d90e5 100644
--- a/arch/x86/kvm/vmx/vmx.h
+++ b/arch/x86/kvm/vmx/vmx.h
@@ -702,12 +702,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] 31+ messages in thread
* [PATCH v5 05/20] KVM: VMX: Refactor instruction information decoding
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (3 preceding siblings ...)
2026-07-20 17:19 ` [PATCH v5 04/20] KVM: VMX: Refactor VMX instruction information access Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 06/20] KVM: VMX: Remove unused control-register access defines Chang S. Bae
` (14 subsequent siblings)
19 siblings, 0 replies; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 UTC (permalink / raw)
To: pbonzini, seanjc; +Cc: kvm, x86, linux-kernel, chang.seok.bae
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.
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
V4 -> V5: Fix handle_vmread() first accesses reg2 (0-day).
---
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 92b530baa0ee..fdec041e95c7 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -5234,7 +5234,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;
@@ -5242,20 +5242,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);
@@ -5664,7 +5664,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)) {
/*
@@ -5712,8 +5712,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,
@@ -5786,8 +5786,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,
@@ -5798,7 +5798,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)
@@ -5975,7 +5975,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;
@@ -5995,8 +5995,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;
@@ -6008,7 +6008,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)
@@ -6055,8 +6055,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 {
@@ -6076,8 +6076,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 &
@@ -6091,7 +6091,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)
@@ -6429,16 +6429,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 8a4b578252c2..c49bc6fabae3 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -6158,8 +6158,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;
@@ -6172,16 +6172,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 a3f58a3d90e5..acb4d7ac8b0a 100644
--- a/arch/x86/kvm/vmx/vmx.h
+++ b/arch/x86/kvm/vmx/vmx.h
@@ -708,14 +708,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] 31+ messages in thread
* [PATCH v5 06/20] KVM: VMX: Remove unused control-register access defines
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (4 preceding siblings ...)
2026-07-20 17:19 ` [PATCH v5 05/20] KVM: VMX: Refactor instruction information decoding Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 07/20] KVM: VMX: Refactor register index retrieval from exit qualification Chang S. Bae
` (13 subsequent siblings)
19 siblings, 0 replies; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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>
---
V4 -> V5: New patch
When looking around DEBUG_REG_ACCESS_REG() for the next patch, this dead
code was discovered.
---
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 ed2ded531e55..30cf48e1c390 100644
--- a/arch/x86/include/asm/vmx.h
+++ b/arch/x86/include/asm/vmx.h
@@ -444,27 +444,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] 31+ messages in thread
* [PATCH v5 07/20] KVM: VMX: Refactor register index retrieval from exit qualification
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (5 preceding siblings ...)
2026-07-20 17:19 ` [PATCH v5 06/20] KVM: VMX: Remove unused control-register access defines Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 08/20] KVM: VMX: Support instruction information extension Chang S. Bae
` (12 subsequent siblings)
19 siblings, 0 replies; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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>
---
V4 -> V5: Switch the argument and convert DEBUG_REG_ACCESS_REG() (review bot [*])
[*]: locally running Sashiko with gemini-3.1-pro-preview
---
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 30cf48e1c390..5645a84f4fbc 100644
--- a/arch/x86/include/asm/vmx.h
+++ b/arch/x86/include/asm/vmx.h
@@ -454,7 +454,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 fdec041e95c7..8e0523e40477 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -6351,7 +6351,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 c49bc6fabae3..e7f968e66bfd 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -5692,7 +5692,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);
@@ -5810,7 +5810,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 acb4d7ac8b0a..f3b0ec7ccb03 100644
--- a/arch/x86/kvm/vmx/vmx.h
+++ b/arch/x86/kvm/vmx/vmx.h
@@ -323,6 +323,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] 31+ messages in thread
* [PATCH v5 08/20] KVM: VMX: Support instruction information extension
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (6 preceding siblings ...)
2026-07-20 17:19 ` [PATCH v5 07/20] KVM: VMX: Refactor register index retrieval from exit qualification Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 18:12 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 09/20] KVM: nVMX: Propagate extended instruction information Chang S. Bae
` (11 subsequent siblings)
19 siblings, 1 reply; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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
---
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 5645a84f4fbc..2334f52071f9 100644
--- a/arch/x86/include/asm/vmx.h
+++ b/arch/x86/include/asm/vmx.h
@@ -276,6 +276,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 f3b0ec7ccb03..2f53338f84f0 100644
--- a/arch/x86/kvm/vmx/vmx.h
+++ b/arch/x86/kvm/vmx/vmx.h
@@ -323,9 +323,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 static_cpu_has(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)
@@ -707,20 +716,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)
@@ -730,37 +741,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] 31+ messages in thread
* [PATCH v5 09/20] KVM: nVMX: Propagate extended instruction information
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (7 preceding siblings ...)
2026-07-20 17:19 ` [PATCH v5 08/20] KVM: VMX: Support instruction information extension Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 18:10 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 10/20] KVM: x86: Support EGPR accessing and tracking for emulator Chang S. Bae
` (10 subsequent siblings)
19 siblings, 1 reply; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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 657f5f743ed9..de90697c4e5a 100644
--- a/arch/x86/kvm/reverse_cpuid.h
+++ b/arch/x86/kvm/reverse_cpuid.h
@@ -35,6 +35,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)
@@ -144,6 +145,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 8e0523e40477..1684f5d1dbf4 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -4766,6 +4766,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] 31+ messages in thread
* [PATCH v5 10/20] KVM: x86: Support EGPR accessing and tracking for emulator
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (8 preceding siblings ...)
2026-07-20 17:19 ` [PATCH v5 09/20] KVM: nVMX: Propagate extended instruction information Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 11/20] KVM: x86: Handle EGPR index and REX2-incompatible opcodes Chang S. Bae
` (9 subsequent siblings)
19 siblings, 0 replies; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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>
---
V4 -> V5: Fix possible shift-out-of-bounds (review bot [*])
[*]: locally running Sashiko with gemini-3.1-pro-preview
---
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] 31+ messages in thread
* [PATCH v5 11/20] KVM: x86: Handle EGPR index and REX2-incompatible opcodes
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (9 preceding siblings ...)
2026-07-20 17:19 ` [PATCH v5 10/20] KVM: x86: Support EGPR accessing and tracking for emulator Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 18:18 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 12/20] KVM: x86: Support REX2-prefixed opcode decode Chang S. Bae
` (8 subsequent siblings)
19 siblings, 1 reply; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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 b566ab5c7515..12a822870d08 100644
--- a/arch/x86/kvm/emulate.c
+++ b/arch/x86/kvm/emulate.c
@@ -175,6 +175,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)
@@ -244,6 +245,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)
@@ -1079,6 +1081,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)
{
@@ -1118,7 +1129,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);
}
@@ -1137,9 +1148,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;
@@ -4247,7 +4258,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 */
@@ -4265,7 +4276,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),
@@ -4289,15 +4300,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 */
@@ -4327,17 +4338,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),
@@ -4378,12 +4389,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 */
@@ -4401,7 +4412,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 */
@@ -4994,6 +5005,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] 31+ messages in thread
* [PATCH v5 12/20] KVM: x86: Support REX2-prefixed opcode decode
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (10 preceding siblings ...)
2026-07-20 17:19 ` [PATCH v5 11/20] KVM: x86: Handle EGPR index and REX2-incompatible opcodes Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 13/20] KVM: x86: Reject EVEX-prefixed instructions Chang S. Bae
` (7 subsequent siblings)
19 siblings, 0 replies; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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 12a822870d08..cb075bd74f76 100644
--- a/arch/x86/kvm/emulate.c
+++ b/arch/x86/kvm/emulate.c
@@ -4258,7 +4258,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 */
@@ -4852,6 +4852,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;
@@ -4905,7 +4916,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 */
@@ -4951,6 +4962,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;
@@ -4973,6 +4995,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 */
@@ -4990,17 +5018,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] 31+ messages in thread
* [PATCH v5 13/20] KVM: x86: Reject EVEX-prefixed instructions
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (11 preceding siblings ...)
2026-07-20 17:19 ` [PATCH v5 12/20] KVM: x86: Support REX2-prefixed opcode decode Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 18:11 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 14/20] KVM: x86: Move KVM_SUPPORTED_{XCR0,XSS} into kvm_x86_vendor_init() Chang S. Bae
` (6 subsequent siblings)
19 siblings, 1 reply; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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 cb075bd74f76..fc7f9f17c1da 100644
--- a/arch/x86/kvm/emulate.c
+++ b/arch/x86/kvm/emulate.c
@@ -5045,6 +5045,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] 31+ messages in thread
* [PATCH v5 14/20] KVM: x86: Move KVM_SUPPORTED_{XCR0,XSS} into kvm_x86_vendor_init()
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (12 preceding siblings ...)
2026-07-20 17:19 ` [PATCH v5 13/20] KVM: x86: Reject EVEX-prefixed instructions Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 15/20] KVM: x86: Guard valid XCR0.APX settings Chang S. Bae
` (5 subsequent siblings)
19 siblings, 0 replies; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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
---
V4 -> V5: New patch (Sean)
---
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 122e46db9a15..1fc7a0f9a13a 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 5578df10179a..84b2dcfcb007 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -191,19 +191,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);
@@ -7009,6 +6997,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] 31+ messages in thread
* [PATCH v5 15/20] KVM: x86: Guard valid XCR0.APX settings
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (13 preceding siblings ...)
2026-07-20 17:19 ` [PATCH v5 14/20] KVM: x86: Move KVM_SUPPORTED_{XCR0,XSS} into kvm_x86_vendor_init() Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 16/20] KVM: x86: Add APX in supported XCR0 Chang S. Bae
` (4 subsequent siblings)
19 siblings, 0 replies; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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 ef69a51ab27f..eb1b2ba1261d 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -5612,8 +5612,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 84b2dcfcb007..266d7a02855b 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -682,6 +682,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] 31+ messages in thread
* [PATCH v5 16/20] KVM: x86: Add APX in supported XCR0
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (14 preceding siblings ...)
2026-07-20 17:19 ` [PATCH v5 15/20] KVM: x86: Guard valid XCR0.APX settings Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 18:25 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 17/20] KVM: x86: Expose APX foundation feature to userspace Chang S. Bae
` (3 subsequent siblings)
19 siblings, 1 reply; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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>
---
V4 -> V5: Separate out from userspace exposure (Sean)
---
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 266d7a02855b..48f8a2d69987 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -7082,6 +7082,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] 31+ messages in thread
* [PATCH v5 17/20] KVM: x86: Expose APX foundation feature to userspace
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (15 preceding siblings ...)
2026-07-20 17:19 ` [PATCH v5 16/20] KVM: x86: Add APX in supported XCR0 Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 18:12 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 18/20] KVM: x86: Expose APX sub-features " Chang S. Bae
` (2 subsequent siblings)
19 siblings, 1 reply; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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/kvm/cpuid.c | 10 +++++++++-
arch/x86/kvm/svm/svm.c | 1 +
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
index 1fc7a0f9a13a..db95f6b578d0 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),
@@ -1442,7 +1446,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);
@@ -1713,6 +1717,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 eb1b2ba1261d..573411777662 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -5593,6 +5593,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] 31+ messages in thread
* [PATCH v5 18/20] KVM: x86: Expose APX sub-features to userspace
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (16 preceding siblings ...)
2026-07-20 17:19 ` [PATCH v5 17/20] KVM: x86: Expose APX foundation feature to userspace Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 18:10 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 19/20] KVM: x86: selftests: Add APX state and ABI test Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 20/20] KVM: x86: selftests: Add APX state handling and XCR0 sanity checks Chang S. Bae
19 siblings, 1 reply; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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 48547842d9fa..7df964f32ba0 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -868,6 +868,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 db95f6b578d0..0339700e885d 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),
@@ -1719,7 +1723,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 de90697c4e5a..cff071ccf926 100644
--- a/arch/x86/kvm/reverse_cpuid.h
+++ b/arch/x86/kvm/reverse_cpuid.h
@@ -67,6 +67,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)
@@ -110,6 +113,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] 31+ messages in thread
* [PATCH v5 19/20] KVM: x86: selftests: Add APX state and ABI test
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (17 preceding siblings ...)
2026-07-20 17:19 ` [PATCH v5 18/20] KVM: x86: Expose APX sub-features " Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
2026-07-20 18:17 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 20/20] KVM: x86: selftests: Add APX state handling and XCR0 sanity checks Chang S. Bae
19 siblings, 1 reply; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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>
---
V4 -> V5: Use vcpu_xsave2_get() instead of vcpu_xsave_get() (review bot [*])
[*]: locally running Sashiko with gemini-3.1-pro-preview
---
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 4ace12606e93..373d685caae2 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -162,6 +162,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 7d3a27bc0d84..5f5acc33486a 100644
--- a/tools/testing/selftests/kvm/include/x86/processor.h
+++ b/tools/testing/selftests/kvm/include/x86/processor.h
@@ -107,6 +107,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 | \
@@ -194,6 +195,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)
@@ -877,6 +879,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 cpu_relax(void)
{
asm volatile("rep; nop" ::: "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] 31+ messages in thread
* [PATCH v5 20/20] KVM: x86: selftests: Add APX state handling and XCR0 sanity checks
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
` (18 preceding siblings ...)
2026-07-20 17:19 ` [PATCH v5 19/20] KVM: x86: selftests: Add APX state and ABI test Chang S. Bae
@ 2026-07-20 17:19 ` Chang S. Bae
19 siblings, 0 replies; 31+ messages in thread
From: Chang S. Bae @ 2026-07-20 17:19 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] 31+ messages in thread
* Re: [PATCH v5 18/20] KVM: x86: Expose APX sub-features to userspace
2026-07-20 17:19 ` [PATCH v5 18/20] KVM: x86: Expose APX sub-features " Chang S. Bae
@ 2026-07-20 18:10 ` sashiko-bot
0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-07-20 18:10 UTC (permalink / raw)
To: Chang S. Bae; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] KVM fails to mask host CPUID values for leaf 0x29, silently exposing raw, unvirtualized host features and unknown future APX sub-features to userspace and the guest VM.
- [Medium] CPUID leaf 0x29 is an indexed leaf but is missing from `cpuid_function_is_indexed()`, causing KVM to return sub-leaf 0's data for all requested sub-leaves.
--
--- Patch [18]: [PATCH v5 18/20] KVM: x86: Expose APX sub-features to userspace ---
commit 86716b3a350e45c75f1b5a6c88353188cae56ce0
Author: Chang S. Bae <chang.seok.bae@intel.com>
KVM: x86: Expose APX sub-features to userspace
This commit adds CPUID leaf 0x29 sub-leaf 0 to expose APX sub-features
(NCI, NDD, NF) to userspace if the APX foundation is available.
> diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
> --- a/arch/x86/kvm/cpuid.c
> +++ b/arch/x86/kvm/cpuid.c
[ ... ]
> @@ -1719,7 +1723,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: High]
If kvm_cpu_cap_has(X86_FEATURE_APX) is true, does this bypass capability
filtering and expose raw host CPUID values for leaf 0x29?
Looking at other leaves in __do_cpuid_func(), they typically use
cpuid_entry_override() or manually clear unused registers to apply the KVM
masks. Without this masking, it appears a userspace VMM querying
KVM_GET_SUPPORTED_CPUID for leaf 0x29 will receive unvirtualized hardware
features, which could then leak into the guest.
[Severity: Medium]
Does cpuid_function_is_indexed() in arch/x86/include/asm/cpuid/api.h also
need to be updated to include 0x29?
Since leaf 0x29 is an indexed leaf (as indicated by the newly added sub-leaf
0), missing it in cpuid_function_is_indexed() means KVM will not set
KVM_CPUID_FLAG_SIGNIFCANT_INDEX.
As a result, if a guest queries an unsupported index like ECX=1,
kvm_find_cpuid_entry2() might incorrectly return the data for sub-leaf 0
instead of treating it as a distinct entry.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260720171949.498680-1-chang.seok.bae@intel.com?part=18
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v5 09/20] KVM: nVMX: Propagate extended instruction information
2026-07-20 17:19 ` [PATCH v5 09/20] KVM: nVMX: Propagate extended instruction information Chang S. Bae
@ 2026-07-20 18:10 ` sashiko-bot
0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-07-20 18:10 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 patch defines KVM_X86_FEATURE_APX but fails to initialize it in KVM's supported capabilities via kvm_cpu_cap_init(), causing the new nested VMCS propagation to be dead code.
--
--- Patch [9]: [PATCH v5 09/20] KVM: nVMX: Propagate extended instruction information ---
commit 2483dfe37a64f0e1963aeef4b3b32d649b5a5dda
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 8e0523e40477d..1684f5d1dbf4d 100644
> --- a/arch/x86/kvm/vmx/nested.c
> +++ b/arch/x86/kvm/vmx/nested.c
[ ... ]
> @@ -4766,6 +4766,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: Medium]
Will this condition ever evaluate to true without first initializing the APX
capability in KVM's supported features?
When userspace sets the guest CPUID via KVM_SET_CPUID2, KVM masks off
capabilities that are not explicitly initialized. It appears F(APX) is missing
from the CPUID_7_1_EDX capability mask in kvm_set_cpu_caps():
arch/x86/kvm/cpuid.c:kvm_set_cpu_caps() {
...
kvm_cpu_cap_init(CPUID_7_1_EDX,
F(AVX_VNNI_INT8),
F(AVX_NE_CONVERT),
F(AMX_COMPLEX),
F(AVX_VNNI_INT16),
F(PREFETCHITI),
F(AVX10),
);
...
}
Does this omission cause the newly added nested VMX propagation for APX to act
as dead code, since the APX bit in vcpu->arch.cpu_caps would always remain 0?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260720171949.498680-1-chang.seok.bae@intel.com?part=9
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v5 13/20] KVM: x86: Reject EVEX-prefixed instructions
2026-07-20 17:19 ` [PATCH v5 13/20] KVM: x86: Reject EVEX-prefixed instructions Chang S. Bae
@ 2026-07-20 18:11 ` sashiko-bot
0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-07-20 18:11 UTC (permalink / raw)
To: Chang S. Bae; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The EVEX prefix check evaluates `ctxt->modrm` without ensuring the ModRM byte was fetched, resulting in a read of stale data, and acts as entirely dead code.
--
--- Patch [13]: [PATCH v5 13/20] KVM: x86: Reject EVEX-prefixed instructions ---
commit fbeae1bf78a009984ff1eb258601fe615d601296
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 cb075bd74f769..fc7f9f17c1da4 100644
> --- a/arch/x86/kvm/emulate.c
> +++ b/arch/x86/kvm/emulate.c
> @@ -5045,6 +5045,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: Low]
Does this evaluate stale data in ctxt->modrm?
When a 32-bit guest executes an instruction starting with 0x62 in
x86_decode_insn(), opcode_table[0x62] yields NotImpl, which lacks the ModRM
flag. Because the ModRM flag is missing, insn_fetch() is bypassed.
Since the EVEX check doesn't short-circuit in 32-bit mode, it will evaluate
whatever stale data remains in ctxt->modrm from a prior emulation on that
context.
Because opcode.flags is already initialized to NotImpl by the table lookup,
is this check entirely redundant?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260720171949.498680-1-chang.seok.bae@intel.com?part=13
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v5 17/20] KVM: x86: Expose APX foundation feature to userspace
2026-07-20 17:19 ` [PATCH v5 17/20] KVM: x86: Expose APX foundation feature to userspace Chang S. Bae
@ 2026-07-20 18:12 ` sashiko-bot
0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-07-20 18:12 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] CPUID leaf 0x29 is not added to cpuid_function_is_indexed(), preventing KVM from properly flagging it as a multi-subleaf CPUID.
--
--- Patch [17]: [PATCH v5 17/20] KVM: x86: Expose APX foundation feature to userspace ---
commit e317b8613a3aae9996b941415cb6b3fe55b0643f
Author: Chang S. Bae <chang.seok.bae@intel.com>
KVM: x86: Expose APX foundation feature to userspace
This commit exposes the APX feature to userspace components and updates the
maximum supported CPUID leaf to 0x29. It clears the APX feature on SVM
environments since EGPR support is not yet implemented.
> diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
> --- a/arch/x86/kvm/cpuid.c
> +++ b/arch/x86/kvm/cpuid.c
[ ... ]
> @@ -1713,6 +1717,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;
[Severity: Medium]
Should CPUID leaf 0x29 also be added to cpuid_function_is_indexed() in
arch/x86/include/asm/cpuid/api.h?
Without adding it there, KVM will not set KVM_CPUID_FLAG_SIGNIFCANT_INDEX
for leaf 0x29. This causes kvm_find_cpuid_entry2() to ignore the
guest-provided ECX sub-leaf index when resolving this leaf.
If a VMM populates leaf 0x29 index 0 with APX features, a guest querying
index 1 will incorrectly receive the features of index 0 instead of
all-zeros.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260720171949.498680-1-chang.seok.bae@intel.com?part=17
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v5 08/20] KVM: VMX: Support instruction information extension
2026-07-20 17:19 ` [PATCH v5 08/20] KVM: VMX: Support instruction information extension Chang S. Bae
@ 2026-07-20 18:12 ` sashiko-bot
0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-07-20 18:12 UTC (permalink / raw)
To: Chang S. Bae; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] The new EXTENDED_INSTRUCTION_INFO VMCS field is omitted from nested VMX reflection (vmcs12), breaking APX instruction emulation for L1.
- [High] Reading EXTENDED_INSTRUCTION_INFO unconditionally when Enlightened VMCS (eVMCS) is enabled triggers a warning and returns 0, corrupting KVM's instruction emulation on Hyper-V.
- [High] Malicious guests can trigger a host WARN_ON_ONCE by executing APX instructions when KVM is compiled without CONFIG_KVM_APX.
--
--- Patch [8]: [PATCH v5 08/20] KVM: VMX: Support instruction information extension ---
commit 96831841dd3c831246d26ed4875bb970e11f9dce
Author: Chang S. Bae <chang.seok.bae@intel.com>
KVM: VMX: Support instruction information extension
This commit defines VMCS field offsets for extended instruction
information and supports retrieving 5-bit register indices for the APX
extension. It relies on the host CPUID APX enumeration for enabling this
behavior.
Link: https://lore.kernel.org/aakEsXJgO-3m2xca@google.com
> diff --git a/arch/x86/include/asm/vmx.h b/arch/x86/include/asm/vmx.h
> index 5645a84f4fbc9..2334f52071f9f 100644
> --- a/arch/x86/include/asm/vmx.h
> +++ b/arch/x86/include/asm/vmx.h
> @@ -276,6 +276,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,
[Severity: High]
Does this break nested VMX instruction parsing?
It appears EXTENDED_INSTRUCTION_INFO is not mapped in
kvm_supported_vmcs12_field_offsets in arch/x86/kvm/vmx/vmcs12.c, nor is
it copied in prepare_vmcs12() in arch/x86/kvm/vmx/nested.c.
If L2 executes an APX-extended instruction that exits to L1, and L1 attempts
a VMREAD of EXTENDED_INSTRUCTION_INFO to parse the exit, will this fail with
VMXERR_UNSUPPORTED_VMCS_COMPONENT and cause L1 emulation failures?
> 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 f3b0ec7ccb03a..2f53338f84f0b 100644
> --- a/arch/x86/kvm/vmx/vmx.h
> +++ b/arch/x86/kvm/vmx/vmx.h
> @@ -323,9 +323,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 static_cpu_has(X86_FEATURE_APX);
> +}
[Severity: High]
Can a guest trigger a host-side WARN_ON_ONCE by executing an APX instruction
when KVM is compiled without CONFIG_KVM_APX?
If KVM is built with NR_VCPU_GENERAL_PURPOSE_REGS = 16, but the host CPU
supports APX, this check returns true. A guest could cause a VM-exit with
a 5-bit register index 16 or greater.
When KVM extracts this index from the instruction and passes it along, will it
hit this check in arch/x86/kvm/regs.h:kvm_register_read_raw():
if (WARN_ON_ONCE((unsigned int)reg >= NR_VCPU_GENERAL_PURPOSE_REGS))
return 0;
This could panic the host if panic_on_warn is enabled.
>
> 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);
> }
[ ... ]
> @@ -707,20 +716,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);
> }
[Severity: High]
Will this corrupt KVM's instruction emulation when Enlightened VMCS (eVMCS)
is enabled on Hyper-V?
When vmx_instr_info_extended() is true, this calls:
vmcs_read64(EXTENDED_INSTRUCTION_INFO)
evmcs_read64()
get_evmcs_offset()
Since EXTENDED_INSTRUCTION_INFO doesn't seem to be added to the EVMCS1_FIELD
mappings in arch/x86/kvm/vmx/hyperv_evmcs.c, get_evmcs_offset() will return
a negative offset.
Will this trigger a warning and cause the read to unconditionally return 0,
leading to incorrect instruction decoding and silent data corruption in the
guest?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260720171949.498680-1-chang.seok.bae@intel.com?part=8
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v5 19/20] KVM: x86: selftests: Add APX state and ABI test
2026-07-20 17:19 ` [PATCH v5 19/20] KVM: x86: selftests: Add APX state and ABI test Chang S. Bae
@ 2026-07-20 18:17 ` sashiko-bot
0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-07-20 18:17 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 away or cache the EGPR reads.
--
--- Patch [19]: [PATCH v5 19/20] KVM: x86: selftests: Add APX state and ABI test ---
commit 7c0f33a315737d86b103b402a97744d82eb69544
Author: Chang S. Bae <chang.seok.bae@intel.com>
KVM: x86: selftests: Add APX state and ABI test
This commit adds a selftest to verify APX-specific behavior and ABI
interactions for EGPR state. It ensures that the state is saved correctly on
VM entry, retained when APX is disabled, and properly exposed 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 7d3a27bc0d842..5f5acc33486ab 100644
> --- a/tools/testing/selftests/kvm/include/x86/processor.h
> +++ b/tools/testing/selftests/kvm/include/x86/processor.h
[ ... ]
> @@ -877,6 +879,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]
Since this inline assembly has an output operand but lacks the volatile
qualifier, can the compiler optimize away or cache the EGPR reads?
When compiled with optimizations, GCC might treat this as a pure function and
reuse the initially cached values across GUEST_SYNC barriers. If userspace
updates the EGPRs in test_userspace_update(), will the guest compare against
stale cached values instead of emitting instructions to re-read the hardware
registers, causing the test to spuriously fail?
> + break;
> + case 17:
> + asm(".byte 0xd5, 0x48, 0x89, 0xc8" : "=a"(data));
> + break;
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260720171949.498680-1-chang.seok.bae@intel.com?part=19
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v5 11/20] KVM: x86: Handle EGPR index and REX2-incompatible opcodes
2026-07-20 17:19 ` [PATCH v5 11/20] KVM: x86: Handle EGPR index and REX2-incompatible opcodes Chang S. Bae
@ 2026-07-20 18:18 ` sashiko-bot
0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-07-20 18:18 UTC (permalink / raw)
To: Chang S. Bae; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [Critical] Missing `NoRex2` flags on vector instructions combined with extended register indices causes out-of-bounds access and host kernel panics (`BUG()`).
- [High] The `NoRex2` mechanism misses numerous REX2-incompatible legacy opcodes and is structurally flawed for instructions within groups, allowing guests to bypass `#UD` injection.
Pre-existing issues:
- [High] KVM calculates an incorrect effective address when a memory operand uses an SIB byte with index=4 (100b) and the REX.X (or REX2.X4) bit is set.
--
--- Patch [11]: [PATCH v5 11/20] KVM: x86: Handle EGPR index and REX2-incompatible opcodes ---
commit 01ac33b49b5ac2e53b61a7aa1ed3d313429a8038
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 b566ab5c75156..12a822870d08e 100644
> --- a/arch/x86/kvm/emulate.c
> +++ b/arch/x86/kvm/emulate.c
> @@ -1118,7 +1129,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);
[Severity: Critical]
Can this extended register index cause a host kernel panic?
With the newly introduced rex_get_rxb(), the register index can now reach 31
if extended bits (e.g. from a REX2 payload) are set.
For SSE/AVX vector instructions, which lack the NoRex2 flag, this index
is passed entirely unmasked to kvm_read_sse_reg() in
__decode_register_operand().
If an extended index > 15 reaches _kvm_read_sse_reg(), it triggers the
default BUG() path:
arch/x86/kvm/fpu.h:_kvm_read_sse_reg() {
...
switch (reg) {
case 0: *data = sse[0]; break;
...
case 15: *data = sse[15]; break;
default: BUG();
}
}
Would an unprivileged guest be able to use a REX2 prefix on a vector
instruction to intentionally panic the host?
[ ... ]
> @@ -1137,9 +1148,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);
[Severity: High]
This is a pre-existing issue, but does this logic calculate an incorrect
effective address when the SIB index is 4 and REX.X (or REX2.X4) is set?
According to the Intel SDM (Vol 2A), when the SIB index field is 100b
(indicating no index register), the REX.X bit must be ignored.
However, KVM initializes index_reg with the REX.X bit (value 8, or up to 24
with REX2). When OR'd with the SIB index of 4 later in decode_modrm(),
index_reg becomes 12 (or 28).
KVM then checks:
if (index_reg != 4)
modrm_ea += reg_read(ctxt, index_reg) << scale;
Since 12 != 4, wouldn't KVM mistakenly read r12 and add it to the effective
address, causing it to read or write incorrect MMIO devices or memory?
[ ... ]
> @@ -4994,6 +5005,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;
[Severity: High]
Does this check fail to inject #UD for numerous REX2-incompatible opcodes?
This check evaluates the NoRex2 flag immediately after reading the first opcode
byte, before the GroupMask evaluation loop.
If the opcode is a group descriptor (e.g. group5 in opcode_table), it does
not have the NoRex2 flag set. The group is resolved later, bypassing this #UD
check entirely. As a result, REX2-incompatible instructions inside groups like
far calls and far jumps cannot be properly trapped.
Additionally, ModR/M-less implicit instructions like hlt, cli, and sti appear
to be missing the NoRex2 flag entirely in opcode_table.
Will this structural flaw allow guests to bypass #UD injection for these
instructions?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260720171949.498680-1-chang.seok.bae@intel.com?part=11
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v5 02/20] KVM: VMX: Save guest EGPRs in VCPU cache
2026-07-20 17:19 ` [PATCH v5 02/20] KVM: VMX: Save guest EGPRs in VCPU cache Chang S. Bae
@ 2026-07-20 18:20 ` sashiko-bot
0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-07-20 18:20 UTC (permalink / raw)
To: Chang S. Bae; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] Guest EGPRs (R16-R31) are saved to `vcpu->arch.regs` on VM-exit but are never synchronized with the guest's XSTATE buffer (`vcpu->arch.guest_fpu`), resulting in complete state loss and data corruption during live migration.
- [Critical] Running KVM C exit handlers with the guest's XCR0 will cause a host #UD panic if the host kernel uses APX instructions and the guest disables APX.
- [High] Guest EGPRs are left in physical registers during the VM-exit speculation window, bypassing the existing mitigation that clears other GPRs.
- [High] Leaving host values in EGPRs (r16-r31) on VM-entry when the guest has not enabled APX creates a speculative information leak from host to guest.
- [Medium] The patch loads guest EGPRs on VM-entry before restoring the guest's SPEC_CTRL, directly contradicting the commit message's safety rationale and breaking the speculation barrier.
--
--- Patch [2]: [PATCH v5 02/20] KVM: VMX: Save guest EGPRs in VCPU cache ---
commit a925787d28df5e47adae8863318b050fa84c23f1
Author: Chang S. Bae <chang.seok.bae@intel.com>
KVM: VMX: Save guest EGPRs in VCPU cache
This commit introduces support for saving and restoring guest EGPRs on
VM exit and entry when the system supports Advanced Performance Extensions
(APX). The registers are explicitly saved and loaded from the
vcpu->arch.regs cache rather than relying on lazy FPU switching.
Link: https://lore.kernel.org/adPRA4ZhnvbaXSn0@google.com
> The register restore path is placed after SPEC_CTRL is restored, so as
> to avoid any mis-speculation into EGPR accesses when APX is disabled.
[Severity: Medium]
Does the placement in the code match this description?
Looking at __vmx_vcpu_run() in arch/x86/kvm/vmx/vmenter.S, the EGPR load
sequence is placed before RESTORE_GUEST_SPEC_CTRL_BODY, which contradicts
this statement and could expose the host to speculative execution using
guest EGPR values.
[ ... ]
> diff --git a/arch/x86/kvm/vmx/vmenter.S b/arch/x86/kvm/vmx/vmenter.S
> index 00d807505fc88..702d86ade6b1d 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
[Severity: High]
Does taking this jump create a speculative information leak?
If the guest disabled APX, we skip loading guest EGPRs but do not clear
the host's values from the physical registers.
While the guest architecturally cannot read these registers without
triggering a #UD, could it execute an APX instruction speculatively before
the #UD is recognized, potentially exfiltrating sensitive host kernel data?
> + 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
[Severity: High]
Are guest EGPRs left in physical registers during the VM-Exit speculation
window?
In vmx_vmexit, KVM clears legacy GPRs at .Lclear_regs earlier in the exit path
to prevent their use in speculative execution.
By deferring the save and clear of EGPRs until here, well after the
speculation barriers (like lfence), are we leaving these 16 guest-controlled
registers accessible during the mitigation execution window?
> + /*
> + * Unlike legacy GPRs, saving could be conditional here on VM-Fail,
> + * which however isn't in fastpath. Instead, simply saving EGPRs always.
> + */
> + 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
[Severity: Critical]
Will this cause data corruption of the EGPR state across live migration?
Because hardware EGPRs are zeroed here, KVM's subsequent host XSAVES in
fpu_swap_kvm_fpstate() will commit zeroes into the guest's FPU state buffer
(vcpu->arch.guest_fpu).
When userspace calls KVM_GET_XSAVE2 to migrate the state, it will copy the
zeroed buffer via fpu_copy_guest_fpstate_to_uabi() since the actual active APX
state stored in vcpu->arch.regs is never synchronized.
On the destination, userspace calls KVM_SET_XSAVE, but __vmx_vcpu_run()
ignores the restored FPU buffer for EGPRs and unconditionally loads from the
uninitialized vcpu->arch.regs, completely losing the state.
> +.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 3681d565f1778..1c86919d14d45 100644
> --- a/arch/x86/kvm/vmx/vmx.c
> +++ b/arch/x86/kvm/vmx/vmx.c
[ ... ]
> @@ -991,6 +991,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;
> +
[Severity: Critical]
Can an unprivileged guest crash the host by clearing XCR0[APX]?
Host XCR0 is restored only after executing a significant amount of
compiler-generated C code. For example, in arch/x86/kvm/x86.c:
vcpu_enter_guest() {
...
exit_fastpath = kvm_x86_call(vcpu_run)(vcpu, run_flags);
...
kvm_load_xfeatures(vcpu, false);
...
}
If the host kernel is compiled with APX support, the compiler may emit EGPR
instructions in these generic C exit handlers (like vmx_vcpu_run).
If the guest disabled APX, the physical CPU will have APX disabled during
VM-Exit. When the host executes these APX instructions before XCR0 is
restored, won't the CPU immediately raise a #UD and panic the kernel?
> return flags;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260720171949.498680-1-chang.seok.bae@intel.com?part=2
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v5 03/20] KVM: x86: Support APX state for XSAVE ABI
2026-07-20 17:19 ` [PATCH v5 03/20] KVM: x86: Support APX state for XSAVE ABI Chang S. Bae
@ 2026-07-20 18:22 ` sashiko-bot
0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-07-20 18:22 UTC (permalink / raw)
To: Chang S. Bae; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] KVM_SET_XSAVE fails to reset APX registers to their architectural INIT state (zero) when the APX bit is missing from the provided XSAVE header.
- [High] Adding APX as a static FPU feature inflates KVM's base uABI size beyond 4096 bytes on AMX-capable hosts, unconditionally breaking legacy KVM_GET_XSAVE and causing out-of-bounds userspace memory reads in KVM_SET_XSAVE.
- [High] kvm_copy_uabi_to_vcpu_regs() modifies vcpu->arch.regs before the rest of the FPU state is validated, leading to a partial VCPU state update if the ioctl fails.
--
--- Patch [3]: [PATCH v5 03/20] KVM: x86: Support APX state for XSAVE ABI ---
commit abf6cc0d5ca657b7c271fda294578cfed25f0046
Author: Chang S. Bae <chang.seok.bae@intel.com>
KVM: x86: Support APX state for XSAVE ABI
Introduce a facility to copy APX state between the VCPU cache and the
userspace buffer as VCPU cache is the single source of truth.
[ ... ]
> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index 0626e835e9eb7..5578df10179ae 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
[ ... ]
> +#ifdef CONFIG_KVM_APX
> +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)
> +{
> + union fpregs_state *xstate = (union fpregs_state *)buf;
> +
> + if (!(xstate->xsave.header.xfeatures & XFEATURE_MASK_APX))
> + return 0;
[Severity: High]
Does this early return fail to reset the APX registers to their architectural
INIT state?
If a VMM restores a vCPU's state using KVM_SET_XSAVE and the
XFEATURE_MASK_APX bit is clear, the APX component should be initialized to
zero. Because this returns early without modifying vcpu->arch.regs, could
this leave stale data in the VCPU's APX registers and leak state across
resets or migrations?
> +
> + if (!(supported_xcr0 & XFEATURE_MASK_APX))
> + return -EINVAL;
[ ... ]
> @@ -3161,6 +3211,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;
[Severity: High]
Will adding APX as a static FPU feature inflate KVM's base uABI size beyond
4096 bytes on AMX-capable hosts, unconditionally breaking legacy KVM_GET_XSAVE?
Because APX (a static feature) is architecturally located after AMX (a
dynamic feature) in the uncompacted XSAVE format, its presence expands
fpu_user_cfg.default_size to > 10KB. KVM expands uabi_size to this value,
which can cause legacy VMMs passing a standard 4096-byte struct kvm_xsave
buffer to trigger an out-of-bounds read in KVM_SET_XSAVE:
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);
...
}
It also appears KVM_GET_XSAVE unconditionally fails because it passes a size
of 4096 to kvm_vcpu_ioctl_x86_get_xsave2(), which rejects it if
size < uabi_size:
arch/x86/kvm/x86.c:kvm_vcpu_ioctl_x86_get_xsave() {
return kvm_vcpu_ioctl_x86_get_xsave2(vcpu, (void *)guest_xsave->region,
sizeof(guest_xsave->region));
}
Is there a way to handle the legacy 4096-byte buffer size gracefully here?
> + int err;
>
> if (fpstate_is_confidential(&vcpu->arch.guest_fpu))
> return vcpu->kvm->arch.has_protected_state ? -EINVAL : 0;
[ ... ]
> @@ -3172,6 +3223,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;
[Severity: High]
Does modifying the APX state before validating the rest of the FPU state
lead to a corrupted vCPU state if the ioctl fails?
Looking at kvm_copy_uabi_to_vcpu_regs(), it copies the APX state directly
into vcpu->arch.regs. If fpu_copy_uabi_to_guest_fpstate() subsequently
fails validation (for example, due to malformed MXCSR reserved bits), the
ioctl aborts and returns an error.
Since the APX registers were already permanently modified, could this violate
transactional ioctl semantics and leave the vCPU in an inconsistent state?
> +
> return fpu_copy_uabi_to_guest_fpstate(&vcpu->arch.guest_fpu,
> guest_xsave->region,
> kvm_caps.supported_xcr0,
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260720171949.498680-1-chang.seok.bae@intel.com?part=3
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v5 16/20] KVM: x86: Add APX in supported XCR0
2026-07-20 17:19 ` [PATCH v5 16/20] KVM: x86: Add APX in supported XCR0 Chang S. Bae
@ 2026-07-20 18:25 ` sashiko-bot
0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-07-20 18:25 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] Enabling APX pushes the default uncompacted FPU uABI size over 4096 bytes, which triggers a WARN_ON_ONCE on every vCPU creation and breaks the legacy KVM_GET_XSAVE API.
--
--- Patch [16]: [PATCH v5 16/20] KVM: x86: Add APX in supported XCR0 ---
commit 0df85d22c18b5d5d318f03dea201f8b5d3746f25
Author: Chang S. Bae <chang.seok.bae@intel.com>
KVM: x86: Add APX in supported XCR0
This commit enables APX support in KVM by setting the APX bit in
kvm_caps.supported_xcr0 when the host has APX enabled and CONFIG_KVM_APX
is configured.
> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index 266d7a02855bb..48f8a2d699879 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
> @@ -7082,6 +7082,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 here push the default
uncompacted FPU uABI size over 4096 bytes?
Because the uncompacted XSAVE layout fixed by CPUID places APX after AMX
(which is 8KB), the offset for APX appears to be over 10.7KB. Since APX is
not an XFD feature, it would be included in the default size calculation.
If fpu_user_cfg.default_size exceeds 4096 bytes (sizeof(struct kvm_xsave)),
it looks like fpu_alloc_guest_fpstate() will trigger a WARN_ON_ONCE on every
vCPU creation, potentially causing a local denial of service if panic_on_warn
is set:
arch/x86/kernel/fpu/core.c:fpu_alloc_guest_fpstate() {
...
gfpu->uabi_size = sizeof(struct kvm_xsave);
if (WARN_ON_ONCE(fpu_user_cfg.default_size > gfpu->uabi_size))
gfpu->uabi_size = fpu_user_cfg.default_size;
...
}
Could this also break the legacy KVM_GET_XSAVE API for userspace?
Once uabi_size exceeds sizeof(struct kvm_xsave), KVM_GET_XSAVE seems to
unconditionally fail:
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;
...
}
> }
>
> if (boot_cpu_has(X86_FEATURE_XSAVES)) {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260720171949.498680-1-chang.seok.bae@intel.com?part=16
^ permalink raw reply [flat|nested] 31+ messages in thread
end of thread, other threads:[~2026-07-20 18:25 UTC | newest]
Thread overview: 31+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-20 17:19 [PATCH v5 00/20] KVM: x86: Enable APX for guests Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 01/20] KVM: x86: Extend VCPU registers for EGPRs Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 02/20] KVM: VMX: Save guest EGPRs in VCPU cache Chang S. Bae
2026-07-20 18:20 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 03/20] KVM: x86: Support APX state for XSAVE ABI Chang S. Bae
2026-07-20 18:22 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 04/20] KVM: VMX: Refactor VMX instruction information access Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 05/20] KVM: VMX: Refactor instruction information decoding Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 06/20] KVM: VMX: Remove unused control-register access defines Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 07/20] KVM: VMX: Refactor register index retrieval from exit qualification Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 08/20] KVM: VMX: Support instruction information extension Chang S. Bae
2026-07-20 18:12 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 09/20] KVM: nVMX: Propagate extended instruction information Chang S. Bae
2026-07-20 18:10 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 10/20] KVM: x86: Support EGPR accessing and tracking for emulator Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 11/20] KVM: x86: Handle EGPR index and REX2-incompatible opcodes Chang S. Bae
2026-07-20 18:18 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 12/20] KVM: x86: Support REX2-prefixed opcode decode Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 13/20] KVM: x86: Reject EVEX-prefixed instructions Chang S. Bae
2026-07-20 18:11 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 14/20] KVM: x86: Move KVM_SUPPORTED_{XCR0,XSS} into kvm_x86_vendor_init() Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 15/20] KVM: x86: Guard valid XCR0.APX settings Chang S. Bae
2026-07-20 17:19 ` [PATCH v5 16/20] KVM: x86: Add APX in supported XCR0 Chang S. Bae
2026-07-20 18:25 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 17/20] KVM: x86: Expose APX foundation feature to userspace Chang S. Bae
2026-07-20 18:12 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 18/20] KVM: x86: Expose APX sub-features " Chang S. Bae
2026-07-20 18:10 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 19/20] KVM: x86: selftests: Add APX state and ABI test Chang S. Bae
2026-07-20 18:17 ` sashiko-bot
2026-07-20 17:19 ` [PATCH v5 20/20] KVM: x86: selftests: Add APX state handling and XCR0 sanity checks Chang S. Bae
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox