From: David Woodhouse <[email protected]>

A VMM that puts an initial VTL context on a higher plane cannot finish
the job through the plane vCPU's fd. kvm_arch_is_vcpu_plane_ioctl()
refuses the ioctls it needs, while the one capability ioctl it does
admit, KVM_ENABLE_CAP, reaches *every* vCPU capability handler.

 - KVM_GET/SET_MP_STATE: a plane vCPU for an AP starts in
   wait-for-INIT like any non-boot x86 vCPU, and nothing ever sends
   INIT/SIPI to the higher plane of an AP. The VMM has to mark it
   runnable itself.
 - KVM_SET_GUEST_DEBUG: single-step and breakpoints on a non-zero
   plane. The debug exits surface through the plane-0 KVM_RUN loop
   like any other plane exit.
 - KVM_X86_SETUP_MCE: mcg_cap is per vCPU and every twin starts with
   the KVM_MAX_MCE_BANKS default, so programming plane 0 alone leaves
   the planes disagreeing. Hyper-V shares MCG state across VTLs.
 - KVM_ENABLE_CAP: accept only the audited capabilities on a plane fd,
   the SynIC pair and the two CPUID-enforcement toggles, which change
   state that each twin keeps separately. Anything else gets -EINVAL, as
   non-allowlisted ioctls already do. The nested paths behind
   enlightened VMCS and direct TLB flush have never been audited for
   plane_level > 0, and without the filter any future vCPU cap would
   work on planes without anyone having decided that it should.

KVM_SET_GUEST_DEBUG on a higher plane can set KVM_GUESTDBG_BLOCKIRQ
there, but the VM-wide APICv BLOCKIRQ inhibit is recomputed over
kvm_for_each_vcpu(), which walks plane 0 only. The inhibit then never
rises for a higher plane, and a plane-0 update drops it while a higher
plane still wants it. Recompute it over every twin. Nothing reaches
this yet (multi-plane VMs are SEV-SNP only, which inhibits APICv
anyway), but it would bite as soon as planes and APICv could coexist.

api.rst marks the new ioctls, and KVM_ENABLE_CAP, as accepted on all
planes, and lists the capabilities a plane fd accepts.

The KVM_ENABLE_CAP filter was found by a local Sashiko review.

The KVM_X86_SETUP_MCE part is from Mushahid Hussain.

Co-developed-by: Mushahid Hussain <[email protected]>
Not-signed-off-by: Mushahid Hussain <[email protected]>
Signed-off-by: David Woodhouse <[email protected]>
---
 Documentation/virt/kvm/api.rst | 14 ++++++++-----
 arch/x86/kvm/x86.c             | 38 ++++++++++++++++++++++++++++++----
 2 files changed, 43 insertions(+), 9 deletions(-)

diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 6c701cda61ef..d21378183c51 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -1494,7 +1494,7 @@ documentation when it pops into existence).
 
 :Capability: KVM_CAP_ENABLE_CAP
 :Architectures: mips, ppc, s390, x86, loongarch
-:Type: vcpu ioctl
+:Type: vcpu ioctl (all planes)
 :Parameters: struct kvm_enable_cap (in)
 :Returns: 0 on success; -1 on error
 
@@ -1515,6 +1515,10 @@ do support it, it only works for extensions that are 
supported for enablement.
 To check if a capability can be enabled, the KVM_CHECK_EXTENSION ioctl should
 be used.
 
+On x86, the vCPU fd of a plane other than plane 0 accepts only
+KVM_CAP_HYPERV_SYNIC, KVM_CAP_HYPERV_SYNIC2, KVM_CAP_HYPERV_ENFORCE_CPUID and
+KVM_CAP_ENFORCE_PV_FEATURE_CPUID; any other capability fails with -EINVAL.
+
 ::
 
   struct kvm_enable_cap {
@@ -1549,7 +1553,7 @@ for vm-wide capabilities.
 
 :Capability: KVM_CAP_MP_STATE
 :Architectures: x86, s390, arm64, riscv, loongarch
-:Type: vcpu ioctl
+:Type: vcpu ioctl (all planes)
 :Parameters: struct kvm_mp_state (out)
 :Returns: 0 on success; -1 on error
 
@@ -1632,7 +1636,7 @@ whether the vcpu is runnable.
 
 :Capability: KVM_CAP_MP_STATE
 :Architectures: x86, s390, arm64, riscv, loongarch
-:Type: vcpu ioctl
+:Type: vcpu ioctl (all planes)
 :Parameters: struct kvm_mp_state (in)
 :Returns: 0 on success; -1 on error
 
@@ -3779,7 +3783,7 @@ handled.
 
 :Capability: KVM_CAP_SET_GUEST_DEBUG
 :Architectures: x86, s390, ppc, arm64
-:Type: vcpu ioctl
+:Type: vcpu ioctl (all planes)
 :Parameters: struct kvm_guest_debug (in)
 :Returns: 0 on success; -1 on error
 
@@ -4623,7 +4627,7 @@ capabilities will have the corresponding bits set.
 
 :Capability: KVM_CAP_MCE
 :Architectures: x86
-:Type: vcpu ioctl
+:Type: vcpu ioctl (all planes)
 :Parameters: u64 mcg_cap (in)
 :Returns: 0 on success,
          -EFAULT if u64 mcg_cap cannot be read,
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 25f574bfbb32..cd0e9b6cf8f7 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -3416,6 +3416,25 @@ 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; 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:
@@ -3468,6 +3487,7 @@ bool kvm_arch_is_vcpu_plane_ioctl(unsigned ioctl)
 {
        switch (ioctl) {
        case KVM_ENABLE_CAP:
+       case KVM_SET_GUEST_DEBUG:
        case KVM_GET_DEBUGREGS:
        case KVM_SET_DEBUGREGS:
        case KVM_GET_LAPIC:
@@ -3480,6 +3500,8 @@ bool kvm_arch_is_vcpu_plane_ioctl(unsigned ioctl)
        case KVM_SET_ONE_REG:
        case KVM_GET_SREGS2:
        case KVM_SET_SREGS2:
+       case KVM_GET_MP_STATE:
+       case KVM_SET_MP_STATE:
        case KVM_GET_VCPU_EVENTS:
        case KVM_SET_VCPU_EVENTS:
        case KVM_GET_XCRS:
@@ -3488,6 +3510,7 @@ bool kvm_arch_is_vcpu_plane_ioctl(unsigned ioctl)
        case KVM_GET_XSAVE2:
        case KVM_SET_XSAVE:
        case KVM_GET_REG_LIST:
+       case KVM_X86_SETUP_MCE:
                return true;
        default:
                return false;
@@ -9355,19 +9378,26 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_task_switch);
 static void kvm_arch_vcpu_guestdbg_update_apicv_inhibit(struct kvm *kvm)
 {
        bool set = false;
-       struct kvm_vcpu *vcpu;
+       struct kvm_vcpu *vcpu, *v;
        unsigned long i;
+       int p;
 
        if (!enable_apicv)
                return;
 
        down_write(&kvm->arch.apicv_update_lock);
 
+       /*
+        * KVM_SET_GUEST_DEBUG is accepted on every plane's vCPU fd, and
+        * kvm_for_each_vcpu() only walks plane 0: look at every twin.
+        */
        kvm_for_each_vcpu(i, vcpu, kvm) {
-               if (vcpu->guest_debug & KVM_GUESTDBG_BLOCKIRQ) {
-                       set = true;
-                       break;
+               vcpu_for_each_plane(vcpu->common, p, v) {
+                       if (v->guest_debug & KVM_GUESTDBG_BLOCKIRQ)
+                               set = true;
                }
+               if (set)
+                       break;
        }
        __kvm_set_or_clear_apicv_inhibit(kvm, APICV_INHIBIT_REASON_BLOCKIRQ, 
set);
        up_write(&kvm->arch.apicv_update_lock);
-- 
2.50.1


Reply via email to