From: David Woodhouse <dwmw@amazon.co.uk> Date: Wed, 8 Nov 2023 21:08:11 +0000 (+0000) Subject: KVM: x86: validate TSC frequency for KVM-wide KVM_SET_TSC_KHZ X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=refs%2Fheads%2Fvalidate-tsc-khz;p=users%2Fdwmw2%2Flinux.git KVM: x86: validate TSC frequency for KVM-wide KVM_SET_TSC_KHZ 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> --- diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index c983c8e434b8b..28850b948bf0f 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -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);