[RFC PATCH 11/55] KVM: x86: Filter KVM_ENABLE_CAP on plane vCPU fds

From: David Woodhouse

Date: Sat Oct 17 2026 - 08:39:59 EST


From: David Woodhouse <dwmw@xxxxxxxxxxxx>

The plane-fd ioctl allowlist admits KVM_ENABLE_CAP wholesale, so a
plane twin's fd reaches every vCPU cap handler: enlightened VMCS,
direct TLB flush, register intercepts. The nested paths behind the
first two have never been audited for plane_level > 0, the register
intercept only ever diverts plane 0 (hv_divert_dt_write() skips higher
planes, so arming it on a twin buys native-emulation overhead and no
diversion), and any future vCPU cap would become plane-enabled by
default without anyone deciding that.

Accept only the audited caps on a plane fd: the per-plane SynIC pair
and the two CPUID-enforcement toggles, which mutate state the planes
genuinely bank per twin. Everything else fails with -EINVAL, matching
how non-allowlisted ioctls already behave on plane fds.

The exposure is widened by our per-plane Hyper-V state: the VTL code
assumes per-vCPU capability state is uniform across a vCPU's twins
unless a cap is explicitly plane-aware.

Found by a local Sashiko review:

### M17 — whole KVM_ENABLE_CAP surface admitted on plane fds
kvm_arch_is_vcpu_plane_ioctl() (x86.c) lists KVM_ENABLE_CAP with no
per-cap filter in kvm_vcpu_ioctl_enable_cap(), so plane fds reach
enable_evmcs, enable_l2_tlb_flush, ENFORCE_CPUID, REG_INTERCEPT etc.
No demonstrated bug (all mutate per-vCPU state), but the vendor
nested paths are unaudited for plane_level>0 and any future vCPU cap
becomes plane-enabled implicitly.
Fix shape: per-cap allowlist for plane vCPUs, or a comment recording
the audit.

Fixes: 21d7ad7e2ba8 ("KVM: Implement KVM_CREATE_VCPU ioctl for planes")
Signed-off-by: David Woodhouse <dwmw@xxxxxxxxxxxx>
Assisted-by: LLM
---
arch/x86/kvm/x86.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 970c738558f2..2c41c53e4d4f 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -6088,6 +6088,26 @@ static int kvm_vcpu_ioctl_enable_cap(struct kvm_vcpu *vcpu,
if (cap->flags)
return -EINVAL;

+ /*
+ * KVM_ENABLE_CAP is accepted on plane vCPU fds, but only the caps
+ * below have been audited for plane_level > 0. The nested paths
+ * behind eVMCS and direct TLB flush have not, and the register
+ * intercept only ever diverts plane 0 (hv_divert_dt_write() skips
+ * higher planes); refuse those rather than implicitly extending
+ * every current and future vCPU cap to planes.
+ */
+ if (vcpu->plane_level) {
+ switch (cap->cap) {
+ case KVM_CAP_HYPERV_SYNIC:
+ case KVM_CAP_HYPERV_SYNIC2:
+ case KVM_CAP_HYPERV_ENFORCE_CPUID:
+ case KVM_CAP_ENFORCE_PV_FEATURE_CPUID:
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
switch (cap->cap) {
#ifdef CONFIG_KVM_HYPERV
case KVM_CAP_HYPERV_SYNIC2:
--
2.50.1