Commit a9bc4a1c authored by Oliver Upton's avatar Oliver Upton
Browse files

KVM: arm64: Reject attempts to set invalid debug arch version



The debug architecture is mandatory in ARMv8, so KVM should not allow
userspace to configure a vCPU with less than that. Of course, this isn't
handled elegantly by the generic ID register plumbing, as the respective
ID register fields have a nonzero starting value.

Add an explicit check for debug versions less than v8 of the
architecture.

Reviewed-by: default avatarMarc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/r/20231003230408.3405722-5-oliver.upton@linux.dev


Signed-off-by: default avatarOliver Upton <oliver.upton@linux.dev>
parent 5a23e5c7
Loading
Loading
Loading
Loading
+29 −3
Original line number Diff line number Diff line
@@ -1216,9 +1216,15 @@ static s64 kvm_arm64_ftr_safe_value(u32 id, const struct arm64_ftr_bits *ftrp,
	/* Some features have different safe value type in KVM than host features */
	switch (id) {
	case SYS_ID_AA64DFR0_EL1:
		if (kvm_ftr.shift == ID_AA64DFR0_EL1_PMUVer_SHIFT)
		switch (kvm_ftr.shift) {
		case ID_AA64DFR0_EL1_PMUVer_SHIFT:
			kvm_ftr.type = FTR_LOWER_SAFE;
			break;
		case ID_AA64DFR0_EL1_DebugVer_SHIFT:
			kvm_ftr.type = FTR_LOWER_SAFE;
			break;
		}
		break;
	case SYS_ID_DFR0_EL1:
		if (kvm_ftr.shift == ID_DFR0_EL1_PerfMon_SHIFT)
			kvm_ftr.type = FTR_LOWER_SAFE;
@@ -1476,14 +1482,22 @@ static u64 read_sanitised_id_aa64pfr0_el1(struct kvm_vcpu *vcpu,
	return val;
}

#define ID_REG_LIMIT_FIELD_ENUM(val, reg, field, limit)			       \
({									       \
	u64 __f_val = FIELD_GET(reg##_##field##_MASK, val);		       \
	(val) &= ~reg##_##field##_MASK;					       \
	(val) |= FIELD_PREP(reg##_##field##_MASK,			       \
			min(__f_val, (u64)reg##_##field##_##limit));	       \
	(val);								       \
})

static u64 read_sanitised_id_aa64dfr0_el1(struct kvm_vcpu *vcpu,
					  const struct sys_reg_desc *rd)
{
	u64 val = read_sanitised_ftr_reg(SYS_ID_AA64DFR0_EL1);

	/* Limit debug to ARMv8.0 */
	val &= ~ID_AA64DFR0_EL1_DebugVer_MASK;
	val |= SYS_FIELD_PREP_ENUM(ID_AA64DFR0_EL1, DebugVer, IMP);
	val = ID_REG_LIMIT_FIELD_ENUM(val, ID_AA64DFR0_EL1, DebugVer, IMP);

	/*
	 * Only initialize the PMU version if the vCPU was configured with one.
@@ -1503,6 +1517,7 @@ static int set_id_aa64dfr0_el1(struct kvm_vcpu *vcpu,
			       const struct sys_reg_desc *rd,
			       u64 val)
{
	u8 debugver = SYS_FIELD_GET(ID_AA64DFR0_EL1, DebugVer, val);
	u8 pmuver = SYS_FIELD_GET(ID_AA64DFR0_EL1, PMUVer, val);

	/*
@@ -1522,6 +1537,13 @@ static int set_id_aa64dfr0_el1(struct kvm_vcpu *vcpu,
	if (pmuver == ID_AA64DFR0_EL1_PMUVer_IMP_DEF)
		val &= ~ID_AA64DFR0_EL1_PMUVer_MASK;

	/*
	 * ID_AA64DFR0_EL1.DebugVer is one of those awkward fields with a
	 * nonzero minimum safe value.
	 */
	if (debugver < ID_AA64DFR0_EL1_DebugVer_IMP)
		return -EINVAL;

	return set_id_reg(vcpu, rd, val);
}

@@ -1543,6 +1565,7 @@ static int set_id_dfr0_el1(struct kvm_vcpu *vcpu,
			   u64 val)
{
	u8 perfmon = SYS_FIELD_GET(ID_DFR0_EL1, PerfMon, val);
	u8 copdbg = SYS_FIELD_GET(ID_DFR0_EL1, CopDbg, val);

	if (perfmon == ID_DFR0_EL1_PerfMon_IMPDEF) {
		val &= ~ID_DFR0_EL1_PerfMon_MASK;
@@ -1558,6 +1581,9 @@ static int set_id_dfr0_el1(struct kvm_vcpu *vcpu,
	if (perfmon != 0 && perfmon < ID_DFR0_EL1_PerfMon_PMUv3)
		return -EINVAL;

	if (copdbg < ID_DFR0_EL1_CopDbg_Armv8)
		return -EINVAL;

	return set_id_reg(vcpu, rd, val);
}