]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
KVM: arm64: Reject attempts to set invalid debug arch version
authorOliver Upton <oliver.upton@linux.dev>
Tue, 3 Oct 2023 23:04:00 +0000 (23:04 +0000)
committerOliver Upton <oliver.upton@linux.dev>
Wed, 4 Oct 2023 17:11:39 +0000 (17:11 +0000)
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: Marc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/r/20231003230408.3405722-5-oliver.upton@linux.dev
Signed-off-by: Oliver Upton <oliver.upton@linux.dev>
arch/arm64/kvm/sys_regs.c

index 34f578d110a98cc6f450ecf4df465e4ed6a6b2a5..1178e2690b77ea49b68ea8e60e8b9ee124065239 100644 (file)
@@ -1216,8 +1216,14 @@ 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)
@@ -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);
 }