[PATCH v6 12/49] KVM: arm64: vgic: Enforce model-specific vCPU limits
From: Sascha Bischoff <hidden>
Date: 2026-09-04 11:41:04
Also in:
kvm, kvmarm
Subsystem:
arm64 port (aarch64 architecture), kernel virtual machine for arm64 (kvm/arm64), the rest · Maintainers:
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton, Linus Torvalds
A GICv5 host with FEAT_GCIE_LEGACY can expose either a native vGICv5 or a vGICv3 device. These models do not necessarily have the same vCPU limit: the native GICv5 limit is probed from the IRS VPE capacity, while the GICv3 limit remains the fixed KVM vGICv3 limit. Keep the IRS-derived limit separately for vGICv5 creation. The pre-VGIC KVM_CAP_MAX_VCPUS value continues to expose the largest limit among the still-selectable models, and kvm_vgic_create() clamps the VM to the limit of the VGIC model userspace actually selected. Userspace can create vCPUs before creating a VGIC. Hence, checking only the number of existing vCPUs after reducing the limit is not sufficient. For example, a single vCPU with ID 500 passes the GICv2 count limit even though GICv2 can represent only eight target CPUs. The GICv2 code subsequently uses the vCPU ID in target and SGI source masks, leading to shifts beyond the width of the operand. GICv5 has a similar requirement because the userspace-provided vcpu_id is used as the index into the VPET. This means that the vcpu_id also represents the IAFFID for a VPE, and therefore is visible to the guest. After selecting the model-specific limit, validate every existing vCPU ID against it. KVM_CREATE_VCPU already enforces the same limit for vCPUs created after the VGIC, making the result independent of creation order. Link: https://lore.kernel.org/r/20260807133051.15D381F000E9@smtp.kernel.org (local) Signed-off-by: Sascha Bischoff <redacted> --- arch/arm64/kvm/vgic/vgic-init.c | 49 ++++++++++++++++++++++++++------- arch/arm64/kvm/vgic/vgic-v5.c | 16 ++++++----- include/kvm/arm_vgic.h | 16 ++++++++--- 3 files changed, 60 insertions(+), 21 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index d5e05d9794076..ccbeb2f783c49 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c@@ -129,13 +129,30 @@ int kvm_vgic_create(struct kvm *kvm, u32 type) } ret = 0; - if (type == KVM_DEV_TYPE_ARM_VGIC_V2) + switch (type) { + case KVM_DEV_TYPE_ARM_VGIC_V2: kvm->max_vcpus = VGIC_V2_MAX_CPUS; - else if (type == KVM_DEV_TYPE_ARM_VGIC_V3) + break; + case KVM_DEV_TYPE_ARM_VGIC_V3: kvm->max_vcpus = VGIC_V3_MAX_CPUS; - else if (type == KVM_DEV_TYPE_ARM_VGIC_V5) - kvm->max_vcpus = min(VGIC_V5_MAX_CPUS, - kvm_vgic_global_state.max_gic_vcpus); + break; + case KVM_DEV_TYPE_ARM_VGIC_V5: + kvm->max_vcpus = kvm_vgic_global_state.max_gicv5_vcpus; + break; + } + + /* + * KVM_CREATE_VCPU enforces the model-specific limit if the VGIC has + * already been created. Apply the same limit to any existing vCPUs so + * that the result does not depend on the order in which userspace + * creates them. + */ + kvm_for_each_vcpu(i, vcpu, kvm) { + if (vcpu->vcpu_id >= kvm->max_vcpus) { + ret = -E2BIG; + goto out_unlock; + } + } if (atomic_read(&kvm->online_vcpus) > kvm->max_vcpus) { ret = -E2BIG;
@@ -399,15 +416,27 @@ int kvm_vgic_vcpu_init(struct kvm_vcpu *vcpu) if (ret) return ret; - /* - * If we are creating a VCPU with a GICv3 we must also register the - * KVM io device for the redistributor that belongs to this VCPU. - */ - if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3) { + switch (dist->vgic_model) { + case KVM_DEV_TYPE_ARM_VGIC_V3: + /* + * If we are creating a VCPU with a GICv3 we must also register + * the KVM io device for the redistributor that belongs to this + * VCPU. + */ mutex_lock(&vcpu->kvm->slots_lock); ret = vgic_register_redist_iodev(vcpu); mutex_unlock(&vcpu->kvm->slots_lock); + break; + case KVM_DEV_TYPE_ARM_VGIC_V5: + /* + * Ensure that it is possible to represent the + * userspace-allocated vcpu_id in the hardware-limited (or + * KVM-capped) VPE table used by GICv5. + */ + if (vcpu->vcpu_id >= kvm_vgic_global_state.max_gicv5_vcpus) + return -EINVAL; } + return ret; }
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 3d8f17301567a..e1571d1788a60 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c@@ -80,7 +80,8 @@ int vgic_v5_probe(const struct gic_kvm_info *info) int ret; kvm_vgic_global_state.type = VGIC_V5; - kvm_vgic_global_state.max_gic_vcpus = VGIC_V5_MAX_CPUS; + kvm_vgic_global_state.max_gic_vcpus = 0; + kvm_vgic_global_state.max_gicv5_vcpus = 0; kvm_vgic_global_state.vcpu_base = 0; kvm_vgic_global_state.vctrl_base = NULL;
@@ -105,8 +106,8 @@ int vgic_v5_probe(const struct gic_kvm_info *info) * Even if the HW supports more per-VM vCPUs, artificially cap as we * can't use them all. */ - kvm_vgic_global_state.max_gic_vcpus = min(vgic_v5_irs_max_vpes(&irs_caps), - VGIC_V5_MAX_CPUS); + kvm_vgic_global_state.max_gicv5_vcpus = min(vgic_v5_irs_max_vpes(&irs_caps), + VGIC_V5_MAX_CPUS); /* * GICv5 requires a set of tables to be allocated in order to manage
@@ -115,7 +116,7 @@ int vgic_v5_probe(const struct gic_kvm_info *info) * we want to run. For now, we match the maximum number offered by the * hardware, but this might not be a wise choice in the long term. */ - ret = vgic_v5_vmt_allocate(kvm_vgic_global_state.max_gic_vcpus); + ret = vgic_v5_vmt_allocate(kvm_vgic_global_state.max_gicv5_vcpus); if (ret) { kvm_err("Failed to allocate the GICv5 VM tables; no GICv5 support\n"); return -ENODEV;
@@ -150,6 +151,8 @@ int vgic_v5_probe(const struct gic_kvm_info *info) } v5_registered = true; + kvm_vgic_global_state.max_gic_vcpus = + kvm_vgic_global_state.max_gicv5_vcpus; kvm_info("GCIE system register CPU interface\n"); skip_v5:
@@ -175,9 +178,8 @@ int vgic_v5_probe(const struct gic_kvm_info *info) return v5_registered ? 0 : ret; } - /* We potentially limit the max VCPUs further than we need to here */ - kvm_vgic_global_state.max_gic_vcpus = min(VGIC_V3_MAX_CPUS, - kvm_vgic_global_state.max_gic_vcpus); + kvm_vgic_global_state.max_gic_vcpus = max(kvm_vgic_global_state.max_gic_vcpus, + VGIC_V3_MAX_CPUS); static_branch_enable(&kvm_vgic_global_state.gicv3_cpuif); kvm_info("GCIE legacy system register CPU interface\n");
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 3ec3f7e1b88b4..593145760812f 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h@@ -172,9 +172,16 @@ struct vgic_global { /* Maintenance IRQ number */ unsigned int maint_irq; - /* maximum number of VCPUs allowed (GICv2 limits us to 8) */ + /* + * Maximum number of VCPUs exposed before userspace has selected a + * VGIC model. Individual VGIC models can impose a lower limit + * (GICv2 limits us to 8). + */ int max_gic_vcpus; + /* Maximum number of VCPUs allowed for a GICv5 VM. */ + int max_gicv5_vcpus; + /* Only needed for the legacy KVM_CREATE_IRQCHIP */ bool can_emulate_gicv2;
@@ -622,10 +629,11 @@ void kvm_vgic_process_async_update(struct kvm_vcpu *vcpu); void vgic_v3_dispatch_sgi(struct kvm_vcpu *vcpu, u64 reg, bool allow_group1); /** - * kvm_vgic_get_max_vcpus - Get the maximum number of VCPUs allowed by HW + * kvm_vgic_get_max_vcpus - Get the pre-VGIC-selection VCPU limit * - * The host's GIC naturally limits the maximum amount of VCPUs a guest - * can use. + * Userspace can query KVM_CAP_MAX_VCPUS before selecting a VGIC model, so + * expose the highest model-specific limit and let kvm_vgic_create() enforce + * the selected model's actual limit. */ static inline int kvm_vgic_get_max_vcpus(void) {
--
2.34.1