]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
KVM: x86: validate TSC frequency for KVM-wide KVM_SET_TSC_KHZ validate-tsc-khz
authorDavid Woodhouse <dwmw@amazon.co.uk>
Wed, 8 Nov 2023 21:08:11 +0000 (21:08 +0000)
committerDavid Woodhouse <dwmw@amazon.co.uk>
Tue, 24 Sep 2024 14:16:51 +0000 (15:16 +0100)
When userspace uses KVM_SET_TSC_KHZ to set the default frequency for all
newly-created vCPUs in the VM, no validation is performed on the requested
frequency. When vCPUs are created, setting the requested TSC frequency may
fail.

Fix that by creating a new calc_tsc_scaling_ratio() function which takes
over most of what the internal set_tsc_khz() used to do, moving a little
more of the sanity checking to it, and using it from the newly-introduced
kvm_set_default_tsc_khz() function as well as
kvm_vcpu_set_tsc_khz().

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
arch/x86/kvm/x86.c

index c983c8e434b8b8eadc8571aebeb06c1b694e134f..28850b948bf0ffd1248781b4d53635d52891ea6f 100644 (file)
@@ -2402,27 +2402,37 @@ static u32 adjust_tsc_khz(u32 khz, s32 ppm)
 
 static void kvm_vcpu_write_tsc_multiplier(struct kvm_vcpu *vcpu, u64 l1_multiplier);
 
-static int set_tsc_khz(struct kvm_vcpu *vcpu, u32 user_tsc_khz, bool scale)
+static int calc_tsc_scaling_ratio(u32 user_tsc_khz, u64 *r_ratio)
 {
+       u32 thresh_lo, thresh_hi;
        u64 ratio;
 
-       /* Guest TSC same frequency as host TSC? */
-       if (!scale) {
-               kvm_vcpu_write_tsc_multiplier(vcpu, kvm_caps.default_tsc_scaling_ratio);
+       /*
+        * Compute the variation in TSC rate which is acceptable
+        * within the range of tolerance and decide if the
+        * rate being applied is within that bounds of the hardware
+        * rate.  If so, no scaling or compensation need be done.
+        */
+       thresh_lo = adjust_tsc_khz(tsc_khz, -tsc_tolerance_ppm);
+       thresh_hi = adjust_tsc_khz(tsc_khz, tsc_tolerance_ppm);
+       if (user_tsc_khz < thresh_lo || user_tsc_khz > thresh_hi) {
+               pr_debug("requested TSC rate %u falls outside tolerance [%u,%u]\n",
+                        user_tsc_khz, thresh_lo, thresh_hi);
+               *r_ratio = kvm_caps.default_tsc_scaling_ratio;
                return 0;
        }
 
        /* TSC scaling supported? */
        if (!kvm_caps.has_tsc_control) {
                if (user_tsc_khz > tsc_khz) {
-                       vcpu->arch.tsc_catchup = 1;
-                       vcpu->arch.tsc_always_catchup = 1;
+                       *r_ratio = 0;
                        return 0;
                } else {
                        pr_warn_ratelimited("user requested TSC rate below hardware speed\n");
-                       return -1;
+                       return -ENOTSUPP;
                }
-       }
+       } else if (user_tsc_khz >= kvm_caps.max_guest_tsc_khz)
+               return -EINVAL;
 
        /* TSC scaling required  - calculate ratio */
        ratio = mul_u64_u32_div(1ULL << kvm_caps.tsc_scaling_ratio_frac_bits,
@@ -2431,45 +2441,58 @@ static int set_tsc_khz(struct kvm_vcpu *vcpu, u32 user_tsc_khz, bool scale)
        if (ratio == 0 || ratio >= kvm_caps.max_tsc_scaling_ratio) {
                pr_warn_ratelimited("Invalid TSC scaling ratio - virtual-tsc-khz=%u\n",
                                    user_tsc_khz);
-               return -1;
+               return -EINVAL;
        }
 
-       kvm_vcpu_write_tsc_multiplier(vcpu, ratio);
+       *r_ratio = ratio;
        return 0;
 }
 
-static int kvm_set_tsc_khz(struct kvm_vcpu *vcpu, u32 user_tsc_khz)
+static int kvm_vcpu_set_tsc_khz(struct kvm_vcpu *vcpu, u32 user_tsc_khz)
 {
-       u32 thresh_lo, thresh_hi;
-       int use_scaling = 0;
+       uint64_t ratio;
+       int ret;
 
        /* tsc_khz can be zero if TSC calibration fails */
        if (user_tsc_khz == 0) {
                /* set tsc_scaling_ratio to a safe value */
                kvm_vcpu_write_tsc_multiplier(vcpu, kvm_caps.default_tsc_scaling_ratio);
-               return -1;
+               return -EINVAL;
        }
 
+       ret = calc_tsc_scaling_ratio(user_tsc_khz, &ratio);
+       if (ret)
+               return ret;
+
        /* Compute a scale to convert nanoseconds in TSC cycles */
        kvm_get_time_scale(user_tsc_khz * 1000LL, NSEC_PER_SEC,
                           &vcpu->arch.virtual_tsc_shift,
                           &vcpu->arch.virtual_tsc_mult);
        vcpu->arch.virtual_tsc_khz = user_tsc_khz;
 
-       /*
-        * Compute the variation in TSC rate which is acceptable
-        * within the range of tolerance and decide if the
-        * rate being applied is within that bounds of the hardware
-        * rate.  If so, no scaling or compensation need be done.
-        */
-       thresh_lo = adjust_tsc_khz(tsc_khz, -tsc_tolerance_ppm);
-       thresh_hi = adjust_tsc_khz(tsc_khz, tsc_tolerance_ppm);
-       if (user_tsc_khz < thresh_lo || user_tsc_khz > thresh_hi) {
-               pr_debug("requested TSC rate %u falls outside tolerance [%u,%u]\n",
-                        user_tsc_khz, thresh_lo, thresh_hi);
-               use_scaling = 1;
+
+       if (ratio) {
+               kvm_vcpu_write_tsc_multiplier(vcpu, ratio);
+       } else {
+               /* Zero means software catchup */
+               vcpu->arch.tsc_catchup = 1;
+               vcpu->arch.tsc_always_catchup = 1;
        }
-       return set_tsc_khz(vcpu, user_tsc_khz, use_scaling);
+
+       return 0;
+}
+
+static int kvm_set_default_tsc_khz(struct kvm *kvm, u32 user_tsc_khz)
+{
+       uint64_t ratio;
+
+       /* Verify that the requested frequency is achievable */
+       if (calc_tsc_scaling_ratio(user_tsc_khz, &ratio))
+               return -EINVAL;
+
+       WRITE_ONCE(kvm->arch.default_tsc_khz, user_tsc_khz);
+
+       return 0;
 }
 
 static u64 compute_guest_tsc(struct kvm_vcpu *vcpu, s64 kernel_ns)
@@ -6155,19 +6178,12 @@ long kvm_arch_vcpu_ioctl(struct file *filp,
        case KVM_SET_TSC_KHZ: {
                u32 user_tsc_khz;
 
-               r = -EINVAL;
                user_tsc_khz = (u32)arg;
 
-               if (kvm_caps.has_tsc_control &&
-                   user_tsc_khz >= kvm_caps.max_guest_tsc_khz)
-                       goto out;
-
                if (user_tsc_khz == 0)
                        user_tsc_khz = tsc_khz;
 
-               if (!kvm_set_tsc_khz(vcpu, user_tsc_khz))
-                       r = 0;
-
+               r = kvm_vcpu_set_tsc_khz(vcpu, user_tsc_khz);
                goto out;
        }
        case KVM_GET_TSC_KHZ: {
@@ -7295,24 +7311,17 @@ set_pit2_out:
        case KVM_SET_TSC_KHZ: {
                u32 user_tsc_khz;
 
-               r = -EINVAL;
                user_tsc_khz = (u32)arg;
 
-               if (kvm_caps.has_tsc_control &&
-                   user_tsc_khz >= kvm_caps.max_guest_tsc_khz)
-                       goto out;
-
                if (user_tsc_khz == 0)
                        user_tsc_khz = tsc_khz;
 
-               WRITE_ONCE(kvm->arch.default_tsc_khz, user_tsc_khz);
-               r = 0;
-
-               goto out;
+               r = kvm_set_default_tsc_khz(kvm, user_tsc_khz);
+               break;
        }
        case KVM_GET_TSC_KHZ: {
                r = READ_ONCE(kvm->arch.default_tsc_khz);
-               goto out;
+               break;
        }
        case KVM_MEMORY_ENCRYPT_OP: {
                r = -ENOTTY;
@@ -12286,7 +12295,7 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)
        vcpu->arch.msr_platform_info = MSR_PLATFORM_INFO_CPUID_FAULT;
        kvm_xen_init_vcpu(vcpu);
        vcpu_load(vcpu);
-       kvm_set_tsc_khz(vcpu, vcpu->kvm->arch.default_tsc_khz);
+       kvm_vcpu_set_tsc_khz(vcpu, vcpu->kvm->arch.default_tsc_khz);
        kvm_vcpu_reset(vcpu, false);
        kvm_init_mmu(vcpu);
        vcpu_put(vcpu);