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