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);