[PATCH v2 22/36] KVM: arm64: gic-v5: Trap and mask guest PPI register accesses

Sascha Bischoff Sascha.Bischoff at arm.com
Fri Dec 19 07:52:43 PST 2025


A guest should not be able to detect if a PPI that is not exposed to
the guest is implemented or not. If the writes to the PPI registers
are not masked, it becomes possible for the guest to detect the
presence of all implemented PPIs on the host.

Guest writes to the following registers are masked:

ICC_CACTIVERx_EL1
ICC_SACTIVERx_EL1
ICC_CPENDRx_EL1
ICC_SPENDRx_EL1
ICC_ENABLERx_EL1
ICC_PRIORITYRx_EL1

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..

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_x_EL2 registers, which is the masked
version of what the guest last wrote.

The ICC_PPI_HMRx_EL1 register is used to determine which PPIs use
Level-sensitive semantics, and which use Edge. For a GICv5 guest, the
correct view of the virtual PPIs must be provided to the guest, and
hence this must also be trapped, but only for reads. The content of
the HMRs is calculated and masked when finalising the PPI state for
the guest.

Signed-off-by: Sascha Bischoff <sascha.bischoff at arm.com>
---
 arch/arm64/kvm/config.c   |  22 ++++++-
 arch/arm64/kvm/sys_regs.c | 133 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 153 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index eb0c6f4d95b6d..f81bfdadd12fb 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -1586,8 +1586,26 @@ static void __compute_ich_hfgrtr(struct kvm_vcpu *vcpu)
 {
 	__compute_fgt(vcpu, ICH_HFGRTR_EL2);
 
-	/* ICC_IAFFIDR_EL1 *always* needs to be trapped when running a guest */
+	/*
+	 * ICC_IAFFIDR_EL1 and ICH_PPI_HMRx_EL1 *always* needs to be
+	 * trapped when running a guest.
+	 **/
 	*vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &= ~ICH_HFGRTR_EL2_ICC_IAFFIDR_EL1;
+	*vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &= ~ICH_HFGRTR_EL2_ICC_PPI_HMRn_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 |
+					     ICH_HFGWTR_EL2_ICC_PPI_PENDRn_EL1 |
+					     ICH_HFGWTR_EL2_ICC_PPI_PRIORITYRn_EL1 |
+					     ICH_HFGWTR_EL2_ICC_PPI_ACTIVERn_EL1);
 }
 
 void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
@@ -1616,6 +1634,6 @@ void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
 		return;
 
 	__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/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 383ada0d75922..cef13bf6bb3a1 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -696,6 +696,111 @@ static bool access_gicv5_iaffid(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
 	return true;
 }
 
+static bool access_gicv5_ppi_hmr(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
+				 const struct sys_reg_desc *r)
+{
+	if (p->is_write)
+		return ignore_write(vcpu, p);
+
+	if (p->Op2 == 0) {	/* ICC_PPI_HMR0_EL1 */
+		p->regval = vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[0];
+	} else {		/* ICC_PPI_HMR1_EL1 */
+		p->regval = vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[1];
+	}
+
+	return true;
+}
+
+static bool access_gicv5_ppi_enabler(struct kvm_vcpu *vcpu,
+				     struct sys_reg_params *p,
+				     const struct sys_reg_desc *r)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+	u64 masked_write;
+
+	/* 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 & cpu_if->vgic_ppi_mask[p->Op2 % 2];
+	cpu_if->vgic_ich_ppi_enabler_entry[p->Op2 % 2] = masked_write;
+
+	return true;
+}
+
+static bool access_gicv5_ppi_pendr(struct kvm_vcpu *vcpu,
+				   struct sys_reg_params *p,
+				   const struct sys_reg_desc *r)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+	u64 masked_write;
+
+	/* 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 & cpu_if->vgic_ppi_mask[p->Op2 % 2];
+
+	if (p->Op2 & 0x2) {	/* SPENDRx */
+		cpu_if->vgic_ppi_pendr_entry[p->Op2 % 2] |= masked_write;
+	} else {		/* CPENDRx */
+		cpu_if->vgic_ppi_pendr_entry[p->Op2 % 2] &= ~masked_write;
+	}
+
+	return true;
+}
+
+static bool access_gicv5_ppi_activer(struct kvm_vcpu *vcpu,
+				     struct sys_reg_params *p,
+				     const struct sys_reg_desc *r)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+	u64 masked_write;
+
+	/* 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 & cpu_if->vgic_ppi_mask[p->Op2 % 2];
+
+	if (p->Op2 & 0x2) {	/* SACTIVERx */
+		cpu_if->vgic_ppi_activer_entry[p->Op2 % 2] |= masked_write;
+	} else {		/* CACTIVERx */
+		cpu_if->vgic_ppi_activer_entry[p->Op2 % 2] &= ~masked_write;
+	}
+
+	return true;
+}
+
+static bool access_gicv5_ppi_priorityr(struct kvm_vcpu *vcpu,
+				     struct sys_reg_params *p,
+				     const struct sys_reg_desc *r)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+	u64 mask, masked_write;
+	unsigned long mask_slice;
+	int i;
+	int reg_idx = ((p->CRm & 0x1) << 3) | p->Op2;
+	int mask_idx = reg_idx >= 8;
+
+	/* We never expect to get here with a read! */
+	if (WARN_ON_ONCE(!p->is_write))
+		return undef_access(vcpu, p, r);
+
+	/* Get the 8 bits of the mask that we care about */
+	mask_slice = (cpu_if->vgic_ppi_mask[mask_idx] >> (reg_idx % 8) * 8) & 0xff;
+
+	/* Generate our mask for the PRIORITYR */
+	mask = 0;
+	for_each_set_bit(i, &mask_slice, 8)
+		mask |= 0x1f << i * 8;
+
+	masked_write = p->regval & mask;
+	vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_priorityr[reg_idx] = masked_write;
+
+	return true;
+}
+
 static bool trap_raz_wi(struct kvm_vcpu *vcpu,
 			struct sys_reg_params *p,
 			const struct sys_reg_desc *r)
@@ -3426,7 +3531,11 @@ static const struct sys_reg_desc sys_reg_descs[] = {
 	{ SYS_DESC(SYS_ICC_AP1R1_EL1), undef_access },
 	{ SYS_DESC(SYS_ICC_AP1R2_EL1), undef_access },
 	{ SYS_DESC(SYS_ICC_AP1R3_EL1), undef_access },
+	{ SYS_DESC(SYS_ICC_PPI_HMR0_EL1), access_gicv5_ppi_hmr },
+	{ SYS_DESC(SYS_ICC_PPI_HMR1_EL1), access_gicv5_ppi_hmr },
 	{ 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 },
@@ -3440,6 +3549,30 @@ static const struct sys_reg_desc sys_reg_descs[] = {
 	{ SYS_DESC(SYS_ICC_SRE_EL1), access_gic_sre },
 	{ SYS_DESC(SYS_ICC_IGRPEN0_EL1), undef_access },
 	{ SYS_DESC(SYS_ICC_IGRPEN1_EL1), undef_access },
+	{ SYS_DESC(SYS_ICC_PPI_CACTIVER0_EL1), access_gicv5_ppi_activer },
+	{ SYS_DESC(SYS_ICC_PPI_CACTIVER1_EL1), access_gicv5_ppi_activer },
+	{ SYS_DESC(SYS_ICC_PPI_SACTIVER0_EL1), access_gicv5_ppi_activer },
+	{ SYS_DESC(SYS_ICC_PPI_SACTIVER1_EL1), access_gicv5_ppi_activer },
+	{ SYS_DESC(SYS_ICC_PPI_CPENDR0_EL1), access_gicv5_ppi_pendr },
+	{ SYS_DESC(SYS_ICC_PPI_CPENDR1_EL1), access_gicv5_ppi_pendr },
+	{ SYS_DESC(SYS_ICC_PPI_SPENDR0_EL1), access_gicv5_ppi_pendr },
+	{ SYS_DESC(SYS_ICC_PPI_SPENDR1_EL1), access_gicv5_ppi_pendr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR0_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR1_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR2_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR3_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR4_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR5_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR6_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR7_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR8_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR9_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR10_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR11_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR12_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR13_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR14_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR15_EL1), access_gicv5_ppi_priorityr },
 
 	{ SYS_DESC(SYS_CONTEXTIDR_EL1), access_vm_reg, reset_val, CONTEXTIDR_EL1, 0 },
 	{ SYS_DESC(SYS_TPIDR_EL1), NULL, reset_unknown, TPIDR_EL1 },
-- 
2.34.1



More information about the linux-arm-kernel mailing list