From: David Woodhouse <[email protected]>

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 <[email protected]>
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


Reply via email to