[PATCH v5 22/36] KVM: arm64: gic-v5: Trap and mask guest ICC_PPI_ENABLERx_EL1 writes

Sascha Bischoff
2026-02-26T16:02:15Z

A guest should not be able to detect if a PPI that is not exposed to
the guest is implemented or not. Avoid the guest enabling any PPIs
that are not implemented as far as the guest is concerned by trapping
and masking writes to the two ICC_PPI_ENABLERx_EL1 registers.

When a guest writes these registers, the write is masked with the set
of PPIs actually exposed to the guest, and the state is written back
to KVM's shadow state. As there is now no way for the guest to change
the PPI enable state without it being trapped, saving of the PPI
Enable state is dropped from guest exit.

Reads for the above registers are not masked. When the guest is
running and reads from the above registers, it is presented with what
KVM provides in the ICH_PPI_ENABLERx_EL2 registers, which is the
masked version of what the guest last wrote.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
---
arch/arm64/include/asm/kvm_host.h | 1 -
arch/arm64/kvm/config.c | 13 +++++++++-
arch/arm64/kvm/hyp/vgic-v5-sr.c | 3 ---
arch/arm64/kvm/sys_regs.c | 43 +++++++++++++++++++++++++++++++
4 files changed, 55 insertions(+), 5 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 60da84071c86e..9af9d96351b89 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -811,7 +811,6 @@ struct kvm_host_data {

/* The saved state of the regs when leaving the guest */
u64 activer_exit[2];
- u64 enabler_exit[2];
} vgic_v5_ppi_state;
};

diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index 5663f25905e83..e14685343191b 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -1699,6 +1699,17 @@ static void __compute_ich_hfgrtr(struct kvm_vcpu *vcpu)
ICH_HFGRTR_EL2_ICC_IDRn_EL1);
}

+static void __compute_ich_hfgwtr(struct kvm_vcpu *vcpu)
+{
+ __compute_fgt(vcpu, ICH_HFGWTR_EL2);
+
+ /*
+ * We present a different subset of PPIs the guest from what
+ * exist in real hardware. We only trap writes, not reads.
+ */
+ *vcpu_fgt(vcpu, ICH_HFGWTR_EL2) &= ~(ICH_HFGWTR_EL2_ICC_PPI_ENABLERn_EL1);
+}
+
void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
{
if (!cpus_have_final_cap(ARM64_HAS_FGT))
@@ -1721,7 +1732,7 @@ void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)

if (cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF)) {
__compute_ich_hfgrtr(vcpu);
- __compute_fgt(vcpu, ICH_HFGWTR_EL2);
+ __compute_ich_hfgwtr(vcpu);
__compute_fgt(vcpu, ICH_HFGITR_EL2);
}
}
diff --git a/arch/arm64/kvm/hyp/vgic-v5-sr.c b/arch/arm64/kvm/hyp/vgic-v5-sr.c
index 47c71c53fcb10..4d20b90031711 100644
--- a/arch/arm64/kvm/hyp/vgic-v5-sr.c
+++ b/arch/arm64/kvm/hyp/vgic-v5-sr.c
@@ -31,9 +31,6 @@ void __vgic_v5_save_ppi_state(struct vgic_v5_cpu_if *cpu_if)
host_data_ptr(vgic_v5_ppi_state)->activer_exit[0] = read_sysreg_s(SYS_ICH_PPI_ACTIVER0_EL2);
host_data_ptr(vgic_v5_ppi_state)->activer_exit[1] = read_sysreg_s(SYS_ICH_PPI_ACTIVER1_EL2);

- host_data_ptr(vgic_v5_ppi_state)->enabler_exit[0] = read_sysreg_s(SYS_ICH_PPI_ENABLER0_EL2);
- host_data_ptr(vgic_v5_ppi_state)->enabler_exit[1] = read_sysreg_s(SYS_ICH_PPI_ENABLER1_EL2);
-
host_data_ptr(vgic_v5_ppi_state)->pendr_exit[0] = read_sysreg_s(SYS_ICH_PPI_PENDR0_EL2);
host_data_ptr(vgic_v5_ppi_state)->pendr_exit[1] = read_sysreg_s(SYS_ICH_PPI_PENDR1_EL2);

diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 589dd31d13c22..740879ecf479e 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -724,6 +724,47 @@ static bool access_gicv5_iaffid(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
return true;
}

+static bool access_gicv5_ppi_enabler(struct kvm_vcpu *vcpu,
+ struct sys_reg_params *p,
+ const struct sys_reg_desc *r)
+{
+ u64 mask = vcpu->kvm->arch.vgic.gicv5_vm.vgic_ppi_mask[p->Op2 % 2];
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+ unsigned long bm_p = 0;
+ u64 masked_write;
+ int i;
+
+ if (!kvm_has_gicv5(vcpu->kvm))
+ return undef_access(vcpu, p, r);
+
+ /* We never expect to get here with a read! */
+ if (WARN_ON_ONCE(!p->is_write))
+ return undef_access(vcpu, p, r);
+
+ masked_write = p->regval & mask;
+ cpu_if->vgic_ppi_enabler[p->Op2 % 2] = masked_write;
+
+ bitmap_from_arr64(&bm_p, &mask, 64);
+
+ /* Sync the change in enable states to the vgic_irqs */
+ for_each_set_bit(i, &bm_p, 64) {
+ struct vgic_irq *irq;
+ u32 intid;
+
+ intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
+ intid |= FIELD_PREP(GICV5_HWIRQ_ID, (p->Op2 % 2) * 64 + i);
+
+ irq = vgic_get_vcpu_irq(vcpu, intid);
+
+ scoped_guard(raw_spinlock_irqsave, &irq->irq_lock)
+ irq->enabled = !!(masked_write & BIT(i));
+
+ vgic_put_irq(vcpu->kvm, irq);
+ }
+
+ return true;
+}
+
static bool trap_raz_wi(struct kvm_vcpu *vcpu,
struct sys_reg_params *p,
const struct sys_reg_desc *r)
@@ -3447,6 +3488,8 @@ static const struct sys_reg_desc sys_reg_descs[] = {
{ SYS_DESC(SYS_ICC_AP1R3_EL1), undef_access },
{ SYS_DESC(SYS_ICC_IDR0_EL1), access_gicv5_idr0 },
{ SYS_DESC(SYS_ICC_IAFFIDR_EL1), access_gicv5_iaffid },
+ { SYS_DESC(SYS_ICC_PPI_ENABLER0_EL1), access_gicv5_ppi_enabler },
+ { SYS_DESC(SYS_ICC_PPI_ENABLER1_EL1), access_gicv5_ppi_enabler },
{ SYS_DESC(SYS_ICC_DIR_EL1), access_gic_dir },
{ SYS_DESC(SYS_ICC_RPR_EL1), undef_access },
{ SYS_DESC(SYS_ICC_SGI1R_EL1), access_gic_sgi },
--
2.34.1