kvm_calc_nested_tsc_multiplier() folds L1's own TSC multiplier (M1) with the multiplier L1 requests for L2 (M2) into the single hardware ratio programmed for L2: M_eff = (M1 * M2) >> frac. Each input is validated individually against the per-vendor maximum (and against zero), but the composed output is not, and it is stored into a 64-bit field. The fixed- point product can leave the representable range at *both* ends: - Top (overflow). Intel (frac = 48): when M1 * M2 >= 2^(64+48) the true M_eff exceeds 2^64 and the low 64 bits are programmed silently, so L2 runs at a wrong rate; the truncated value can even land on default_tsc_scaling_ratio (2^48), disabling scaling entirely, or on 0, which fails the nested VM-entry. AMD (frac = 32): the ratio MSR is 40 bits, but M_eff can reach ~2^48, setting reserved bits that architecturally #GP the physical MSR_AMD64_TSC_RATIO write (see the companion wrmsrq_safe() change). This is reachable with no adversary on live migration: M1 = (guest_khz << 48) / host_khz grows on a slower destination, M_eff is recomposed on KVM_SET_NESTED_STATE / the destination KVM_SET_TSC_KHZ with the same missing check, and a (M1, M2) legal on the source can recompose to M_eff == 0 on the destination, whose mid-flight resume fails hardware VM-entry on vmcs02 and tears the guest down (KVM_EXIT_SHUTDOWN). - Bottom (underflow). The product also floors to 0 when the true ratio is below one ulp: M1 < 2^frac (L1 slower than the host) composed with a small but legal M2 -- e.g. M2 == 1 -- yields (M1 * M2) >> frac == 0. Both inputs pass their individual checks (Intel rejects only M2 == 0 via CC(!vmcs12->tsc_multiplier); a zero M2 on AMD is rejected separately), yet compose to a zero M_eff. A zero multiplier is not representable: on Intel the SDM requires a nonzero TSC-multiplier under "use TSC scaling", so it fails VM-entry; on AMD it programs a frozen ratio MSR. KVM already rejects a zero *input* multiplier at nested-entry validation; extend that to the composed *result*. Compute the full 128-bit product, saturate to kvm_caps.max_tsc_scaling_ratio when it overflows the field or exceeds the advertised maximum, and clamp up to the smallest representable nonzero ratio (1) when it underflows to zero -- keeping the guest alive at a representable ratio instead of programming an illegal one. This runs on both the entry compose (prepare_vmcs02 / nested_svm_update_tsc_ratio_msr) and the restore/recompute paths that share this helper (kvm_vcpu_write_tsc_multiplier, KVM_SET_NESTED_STATE). Signed-off-by: Amirmohammad Eftekhar --- v1: https://lore.kernel.org/r/20260722084702.1028983-1-amirmohammad.eftekhar@cispa.de The v1 review flagged that bounding only the top end leaves the composed multiplier able to underflow to 0 (M1 < 2^frac composed with a small but legal M2), which is the same illegal-value / VM-entry-failure class this patch exists to prevent -- so it belongs in the same helper. Note this is distinct from a zero *input* MSR on AMD (svm_set_msr() accepting data == 0), which is addressed separately by "KVM: SVM: reject a zero TSC ratio in MSR_AMD64_TSC_RATIO"; both of this compose's inputs are individually legal. v1 -> v2: - also clamp the composed multiplier's low end: return 1 (the smallest representable nonzero ratio) when the fixed-point product floors to 0, mirroring the high-end saturation. Reworded the changelog to cover both ends and retitled ("... to the max ratio" -> "... to a representable ratio") to reflect that it now bounds both. - no change to the high-end / overflow logic. arch/x86/kvm/x86.c | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index afcac1042947..a65d23cbb49b 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -2675,11 +2675,45 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_calc_nested_tsc_offset); u64 kvm_calc_nested_tsc_multiplier(u64 l1_multiplier, u64 l2_multiplier) { - if (l2_multiplier != kvm_caps.default_tsc_scaling_ratio) - return mul_u64_u64_shr(l1_multiplier, l2_multiplier, - kvm_caps.tsc_scaling_ratio_frac_bits); + unsigned int frac = kvm_caps.tsc_scaling_ratio_frac_bits; + u64 nested_multiplier; - return l1_multiplier; + if (l2_multiplier == kvm_caps.default_tsc_scaling_ratio) + return l1_multiplier; + + /* + * Both inputs are individually within [1, max_tsc_scaling_ratio], but + * their fixed-point product (M1 * M2) >> frac can exceed the width of + * the hardware TSC multiplier field: on Intel the high bits would be + * silently truncated (yielding a wrong -- or even zero -- L2 scaling + * ratio), and on AMD the surplus bits are reserved bits that #GP the + * physical MSR_AMD64_TSC_RATIO write. (M1 * M2) >> frac overflows u64 + * iff the high 64 bits of the 128-bit product are >= (1 << frac); + * detect that (and any result above the advertised maximum) and + * saturate rather than program an unrepresentable ratio. + */ + if (mul_u64_u64_shr(l1_multiplier, l2_multiplier, 64) >= (1ULL << frac)) + return kvm_caps.max_tsc_scaling_ratio; + + nested_multiplier = mul_u64_u64_shr(l1_multiplier, l2_multiplier, frac); + if (nested_multiplier > kvm_caps.max_tsc_scaling_ratio) + return kvm_caps.max_tsc_scaling_ratio; + + /* + * The same product can floor to 0 at the other end, when the true ratio + * is below one ulp (e.g. M1 < 2^frac -- L1 slower than the host -- + * composed with a small but legal M2). A zero multiplier is no more + * representable than an oversized one: Intel rejects it at VM-entry (the + * SDM requires a nonzero TSC-multiplier under "use TSC scaling", which + * KVM already mirrors on the input via CC(!vmcs12->tsc_multiplier)), and + * on AMD it programs a frozen ratio into MSR_AMD64_TSC_RATIO. Clamp up + * to the smallest representable nonzero ratio, mirroring the saturation + * above. + */ + if (!nested_multiplier) + return 1; + + return nested_multiplier; } EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_calc_nested_tsc_multiplier); -- 2.34.1