From: sashiko-bot@kernel.org
To: "David Woodhouse" <dwmw2@infradead.org>, kvm@vger.kernel.org
Subject: Re: [PATCH v11 14/23] KVM: x86: Add KVM_[GS]ET_CLOCK_GUEST for accurate KVM clock migration
Date: Thu, 05 Nov 2026 19:51:35 +0000 [thread overview]
Message-ID: <sashiko-outbox-200022@kernel.org> (raw)
In-Reply-To: <20261105154955.2525419-15-dwmw2@infradead.org>
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] KVM: x86: stale masterclock state in kvm_vcpu_ioctl_get_clock_guest()
- [High] KVM: x86: u64 underflow and overflow in kvm_vcpu_ioctl_set_clock_guest()
- [High] KVM: x86: unsigned TSC wraparound check in kvm_vcpu_ioctl_set_clock_guest()
- [High] KVM: x86: premature all_vcpus_matched_freq in kvm_track_tsc_matching()
- [Medium] KVM: x86: unreachable -EBUSY path in kvm_vcpu_ioctl_set_clock_guest()
- [Medium] KVM: x86: early return inside pvclock_sc seqcount read section in kvm_vcpu_ioctl_get_clock_guest()
--
commit a2ceb951545e5e2e7837aa6b7c533b2b547ba201
Author: Jack Allister <jalliste@amazon.com>
KVM: x86: Add KVM_[GS]ET_CLOCK_GUEST for accurate KVM clock migration
This commit adds the per-vCPU KVM_GET_CLOCK_GUEST and KVM_SET_CLOCK_GUEST
ioctls (advertised via KVM_CAP_CLOCK_GUEST) to save and restore the
pvclock_vcpu_time_info structure in terms of the guest vCPU TSC when the VM
is using the master clock.
> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index 6dbdb60962fe7..bdc838b12fdc0 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
[ ... ]
> @@ -3541,6 +3544,168 @@ static int kvm_vcpu_ioctl_enable_cap(struct kvm_vcpu *vcpu,
> }
> }
>
> +#ifdef CONFIG_X86_64
> +static int kvm_vcpu_ioctl_get_clock_guest(struct kvm_vcpu *v, void __user *argp)
> +{
> + struct pvclock_vcpu_time_info hv_clock = {};
> + struct kvm_arch *ka = &v->kvm->arch;
> + unsigned int seq;
> +
> + /*
> + * Construct the pvclock purely from the master clock state. The
> + * master mul/shift are computed for the guest TSC frequency, which
> + * in master clock mode is the frequency of every vCPU; only the
> + * tsc_timestamp is per-vCPU, translating the master snapshot into
> + * this vCPU's TSC domain via its scaling ratio and offset.
> + *
> + * Note, this deliberately does NOT invoke kvm_guest_time_update(),
> + * which would write the guest's pvclock pages: dirtying guest
> + * memory after the final KVM_RUN would break post-copy migration
> + * flows. The pvclock returned here describes the same clock as the
> + * guest-visible copy (the same linear function of the guest TSC),
> + * anchored at the current master clock snapshot.
> + */
> + do {
> + seq = read_seqcount_begin(&ka->pvclock_sc);
> +
> + if (!ka->use_master_clock)
> + return -ENODATA;
[Severity: Medium]
Is it safe to return directly from inside the read_seqcount_begin() loop
when !ka->use_master_clock without calling read_seqcount_retry()?
Returning early here skips the pairing smp_rmb() and sequence validation on
ka->use_master_clock (which pvclock_update_vm_gtod_copy() updates inside
write_seqcount_begin/end and may transiently set to true before clearing it
if tsc_hz is 0), and leaves KCSAN's seqlock atomic region state active on
current because do___read_seqcount_retry() -> kcsan_atomic_next(0) is never
reached.
For comparison, kvm_guest_time_update() in arch/x86/kvm/x86.c completes the
seqcount loop before checking use_master_clock:
do {
seq = read_seqcount_begin(&ka->pvclock_sc);
use_master_clock = ka->use_master_clock;
if (!use_master_clock)
continue;
...
} while (read_seqcount_retry(&ka->pvclock_sc, seq));
> +
> + hv_clock.tsc_timestamp = kvm_read_l1_tsc(v, ka->master_cycle_now);
> + hv_clock.system_time = ka->master_kernel_ns + ka->kvmclock_offset;
> + hv_clock.tsc_shift = ka->master_tsc_shift;
> + hv_clock.tsc_to_system_mul = ka->master_tsc_mul;
> + hv_clock.flags = READ_ONCE(ka->all_vcpus_matched_tsc) ?
> + PVCLOCK_TSC_STABLE_BIT : 0;
> + } while (read_seqcount_retry(&ka->pvclock_sc, seq));
[Severity: High]
Can kvm_vcpu_ioctl_get_clock_guest() return a corrupt pvclock_vcpu_time_info
structure when KVM_REQ_MASTERCLOCK_UPDATE is pending?
Operations that change vCPU TSC frequency, TSC offset, or masterclock
eligibility — such as KVM_SET_TSC_KHZ via kvm_set_tsc_khz() ->
kvm_synchronize_tsc() -> __kvm_synchronize_tsc() -> kvm_track_tsc_matching(),
host-initiated MSR_IA32_TSC writes, KVM_VCPU_TSC_OFFSET via
kvm_arch_tsc_set_attr(), host clocksource updates via
pvclock_gtod_update_fn(), or backwards TSC detection — immediately update
v->arch.l1_tsc_scaling_ratio, v->arch.l1_tsc_offset,
ka->cur_tsc_scaling_ratio, and ka->all_vcpus_matched_freq, but defer
refreshing ka->use_master_clock, ka->master_tsc_shift, ka->master_tsc_mul,
ka->master_cycle_now, and ka->master_kernel_ns by queuing
KVM_REQ_MASTERCLOCK_UPDATE:
arch/x86/kvm/x86.c:kvm_track_tsc_matching() {
...
if ((ka->use_master_clock && update_mclock) ||
(ka->use_master_clock != use_master_clock))
kvm_make_request(KVM_REQ_MASTERCLOCK_UPDATE, vcpu);
}
Because KVM_REQ_MASTERCLOCK_UPDATE is only serviced in vcpu_enter_guest()
during KVM_RUN (and kvm_track_tsc_matching() only queues it on the single
vCPU being updated rather than VM-wide), invoking KVM_GET_CLOCK_GUEST while
vCPUs are stopped in userspace reads stale ka->use_master_clock,
ka->master_tsc_shift, and ka->master_tsc_mul while combining them with the
updated v->arch.l1_tsc_scaling_ratio and v->arch.l1_tsc_offset in
kvm_read_l1_tsc(v, ka->master_cycle_now). This either mixes a newly scaled
tsc_timestamp with old master_tsc_mul/shift (causing KVM_SET_CLOCK_GUEST to
fail with -ERANGE) or returns 0 with stale clock data instead of -ENODATA
after masterclock eligibility was lost.
[Severity: High]
Also on this read path in kvm_vcpu_ioctl_get_clock_guest(), can
ka->use_master_clock remain true even when vCPUs have mismatched TSC
frequencies?
If userspace creates vCPU 0 (online_vcpus == 1) and sets its TSC frequency
via KVM_SET_TSC_KHZ to F1 != ka->default_tsc_khz, ka->cur_tsc_khz is F1 and
ka->all_vcpus_matched_freq is true.
When vCPU 1 is subsequently created in kvm_vm_ioctl_create_vcpu(),
kvm_arch_vcpu_create() runs before atomic_inc(&kvm->online_vcpus) (while
online_vcpus is still 1) and calls kvm_set_tsc_khz(vcpu1, default_tsc_khz) ->
kvm_synchronize_tsc() -> __kvm_synchronize_tsc():
if (vcpu->arch.virtual_tsc_khz != kvm->arch.cur_tsc_khz) {
kvm->arch.cur_tsc_freq_generation++;
kvm->arch.all_vcpus_matched_freq = false;
kvm->arch.nr_vcpus_matched_freq = 0;
}
/* Count each vCPU once per freq generation */
if (vcpu->arch.this_tsc_freq_generation != kvm->arch.cur_tsc_freq_generation) {
vcpu->arch.this_tsc_freq_generation = kvm->arch.cur_tsc_freq_generation;
kvm->arch.nr_vcpus_matched_freq++;
}
kvm->arch.cur_tsc_khz = vcpu->arch.virtual_tsc_khz;
__kvm_synchronize_tsc() sees default_tsc_khz != F1, resets
nr_vcpus_matched_freq to 1, updates ka->cur_tsc_khz to default_tsc_khz, and
calls kvm_track_tsc_matching():
if (ka->all_vcpus_matched_tsc ||
ka->nr_vcpus_matched_freq >= online)
ka->all_vcpus_matched_freq = true;
Because online is still 1 during kvm_arch_vcpu_create(), 1 >= 1 is true and
ka->all_vcpus_matched_freq is latched back to true. When
kvm_arch_vcpu_postcreate() later runs after online_vcpus is incremented to 2,
vcpu1->arch.virtual_tsc_khz already matches ka->cur_tsc_khz
(default_tsc_khz), and kvm_track_tsc_matching() never clears
ka->all_vcpus_matched_freq when nr_vcpus_matched_freq < online.
As a result, ka->all_vcpus_matched_freq and ka->use_master_clock stay true
while vCPU 0 is at F1 and vCPU 1 is at default_tsc_khz, causing
kvm_vcpu_ioctl_get_clock_guest() on vCPU 0 to return a pvclock structure with
tsc_timestamp scaled for F1 and tsc_to_system_mul/tsc_shift computed for
default_tsc_khz instead of returning -ENODATA.
> +
> + if (copy_to_user(argp, &hv_clock, sizeof(hv_clock)))
> + return -EFAULT;
> +
> + return 0;
> +}
> +
> +/*
> + * Reverse the calculation in the hv_clock definition.
> + *
> + * time_ns = ( (cycles << shift) * mul ) >> 32;
> + * (although shift can be negative, so that's bad C)
> + *
> + * So for a single second,
> + * NSEC_PER_SEC = ( ( FREQ_HZ << shift) * mul ) >> 32
> + * NSEC_PER_SEC << 32 = ( FREQ_HZ << shift ) * mul
> + * ( NSEC_PER_SEC << 32 ) / mul = FREQ_HZ << shift
> + * ( NSEC_PER_SEC << 32 ) / mul ) >> shift = FREQ_HZ
> + */
> +static u64 hvclock_to_hz(u32 mul, s8 shift)
> +{
> + u64 tm = NSEC_PER_SEC << 32;
> +
> + /* Maximise precision. Shift left until the top bit is set */
> + tm <<= 2;
> + shift += 2;
> +
> + /* While 'mul' is even, increase the shift *after* the division */
> + while (!(mul & 1)) {
> + shift++;
> + mul >>= 1;
> + }
> +
> + tm /= mul;
> +
> + if (shift >= 64)
> + return 0;
> + if (shift > 0)
> + return tm >> shift;
> + if (shift <= -64)
> + return 0;
> + return tm << -shift;
> +}
> +
> +static int kvm_vcpu_ioctl_set_clock_guest(struct kvm_vcpu *v, void __user *argp)
> +{
> + struct pvclock_vcpu_time_info user_hv_clock;
> + struct kvm *kvm = v->kvm;
> + struct kvm_arch *ka = &kvm->arch;
> + u64 curr_tsc_hz, user_tsc_hz;
> + u64 user_clk_ns;
> + u64 guest_tsc;
> + int rc = 0;
> +
> + if (copy_from_user(&user_hv_clock, argp, sizeof(user_hv_clock)))
> + return -EFAULT;
> +
> + if (user_hv_clock.pad0 || user_hv_clock.pad[0] || user_hv_clock.pad[1])
> + return -EINVAL;
> +
> + if (!user_hv_clock.tsc_to_system_mul)
> + return -EINVAL;
> +
> + if (user_hv_clock.tsc_shift < -31 || user_hv_clock.tsc_shift > 31)
> + return -EINVAL;
> +
> + user_tsc_hz = hvclock_to_hz(user_hv_clock.tsc_to_system_mul,
> + user_hv_clock.tsc_shift);
> +
> + kvm_hv_request_tsc_page_update(kvm);
> +
> + /*
> + * kvm_start_pvclock_update() takes tsc_write_lock and opens
> + * the pvclock seqcount; kvm_end_pvclock_update() closes both.
> + * All clock state modifications between them are atomic with
> + * respect to readers in kvm_guest_time_update().
> + */
> + kvm_start_pvclock_update(kvm);
> + pvclock_update_vm_gtod_copy(kvm);
> +
> + if (!ka->use_master_clock) {
> + rc = -ENODATA;
> + goto out;
> + }
> +
> + curr_tsc_hz = (u64)get_cpu_tsc_khz() * HZ_PER_KHZ;
> + if (unlikely(curr_tsc_hz == 0)) {
> + rc = -EBUSY;
> + goto out;
> + }
[Severity: Medium]
Can this curr_tsc_hz == 0 (-EBUSY) branch ever be reached?
kvm_start_pvclock_update() acquires raw_spin_lock_irq(&ka->tsc_write_lock),
disabling interrupts and preemption on the local CPU. Immediately before the
!ka->use_master_clock check, pvclock_update_vm_gtod_copy() reads
get_cpu_tsc_khz() on the same CPU and clears ka->use_master_clock if tsc_hz
is 0:
arch/x86/kvm/x86.c:pvclock_update_vm_gtod_copy() {
...
tsc_hz = (u64)get_cpu_tsc_khz() * HZ_PER_KHZ;
if (tsc_hz && kvm_caps.has_tsc_control)
tsc_hz = kvm_scale_tsc(tsc_hz,
ka->master_tsc_scaling_ratio);
if (tsc_hz)
kvm_get_time_scale(NSEC_PER_SEC, tsc_hz,
&ka->master_tsc_shift,
&ka->master_tsc_mul);
else
ka->use_master_clock = false;
}
Because IRQs remain disabled across both calls, get_cpu_tsc_khz() == 0 will
always set ka->use_master_clock = false in pvclock_update_vm_gtod_copy(),
causing kvm_vcpu_ioctl_set_clock_guest() to take the !ka->use_master_clock
path and return -ENODATA rather than the documented -EBUSY.
> +
> + if (kvm_caps.has_tsc_control)
> + curr_tsc_hz = kvm_scale_tsc(curr_tsc_hz,
> + v->arch.l1_tsc_scaling_ratio);
> +
> + /*
> + * The mul/shift in the provided pvclock structure encode the guest
> + * TSC frequency at which it was generated. Sanity-check that it is
> + * consistent with this vCPU's effective TSC frequency, allowing a
> + * discrepancy of 1 kHz either way since independently calibrated
> + * hosts will not measure precisely the same value even for the
> + * same nominal frequency.
> + */
> + if (user_tsc_hz < curr_tsc_hz - 1000 ||
> + user_tsc_hz > curr_tsc_hz + 1000) {
> + rc = -ERANGE;
> + goto out;
> + }
[Severity: High]
Are there three boundary and overflow issues in the frequency calculation and
comparison between hvclock_to_hz() and kvm_vcpu_ioctl_set_clock_guest()?
First, if a vCPU's guest TSC frequency is set to 1 kHz via KVM_SET_TSC_KHZ,
set_tsc_khz() computes ratio = mul_u64_u32_div(1ULL << frac_bits, 1,
tsc_khz), which truncates down. Scaling the host TSC frequency in Hz via
kvm_scale_tsc() then yields curr_tsc_hz == 999 Hz (non-zero, so
ka->use_master_clock stays true). Because curr_tsc_hz is u64, evaluating
curr_tsc_hz - 1000 underflows to U64_MAX, making user_tsc_hz < curr_tsc_hz -
1000 unconditionally true and failing with -ERANGE.
Second, host tsc_khz is measured in whole kHz (1000 Hz steps), so a 1 kHz
calibration difference between two hosts is already 1000 Hz. Because
kvm_get_time_scale() floors tsc_to_system_mul via div_frac(), hvclock_to_hz()
rounds user_tsc_hz up by 1 to 3 Hz (for example, 2100001 kHz becomes
2100001001 Hz) while kvm_scale_tsc() can floor curr_tsc_hz by 1 Hz. Doesn't a
1 kHz host calibration difference therefore exceed +/- 1000 Hz by 1 to 3 Hz
and get rejected with -ERANGE?
Third, in hvclock_to_hz(), tm = (NSEC_PER_SEC << 34) / mul always has at
least bit 31 set (and bit 63 set when mul == 1). When shift < 0 (for example,
user_hv_clock.tsc_shift in [-31, -3]), won't tm << -shift overflow 64 bits
and wrap around modulo 2^64?
> +
> + /*
> + * Calculate the guest TSC at the new reference point, and the
> + * corresponding KVM clock value according to user_hv_clock.
> + * Adjust kvmclock_offset so both definitions agree.
> + */
> + guest_tsc = kvm_read_l1_tsc(v, ka->master_cycle_now);
> + if (guest_tsc < user_hv_clock.tsc_timestamp) {
> + rc = -EINVAL;
> + goto out;
> + }
[Severity: High]
Does the unsigned comparison guest_tsc < user_hv_clock.tsc_timestamp mishandle
64-bit modular TSC wraparound?
Guest TSC values from kvm_read_l1_tsc() and pvclock deltas in
__pvclock_read_cycles() (u64 delta = tsc - src->tsc_timestamp) use 64-bit
modular arithmetic. When a vCPU has a negative TSC offset, its guest TSC can
start near U64_MAX and wrap across 0 into small positive values as time
advances.
If user_hv_clock.tsc_timestamp was captured just before wrapping across 0
(near U64_MAX) and guest_tsc on the destination is just after 0, guest_tsc is
ahead of tsc_timestamp, but guest_tsc < user_hv_clock.tsc_timestamp evaluates
to true and rejects the call with -EINVAL. Conversely, if tsc_timestamp is a
small value just above 0 and guest_tsc is slightly behind it (wrapped below 0
to near U64_MAX), the unsigned check evaluates to false and passes a huge
delta (~U64_MAX) into __pvclock_read_cycles().
Should this check (s64)(guest_tsc - user_hv_clock.tsc_timestamp) < 0 instead?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261105154955.2525419-1-dwmw2@infradead.org?part=14
next prev parent reply other threads:[~2026-11-05 19:51 UTC|newest]
Thread overview: 32+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-05 15:49 [PATCH v11 00/23] KVM: x86: Cleaning up the KVM clock mess, part 2 David Woodhouse
2026-11-05 15:49 ` [PATCH v11 01/23] KVM: x86: Improve synchronization in kvm_synchronize_tsc() David Woodhouse
2026-11-05 15:49 ` [PATCH v11 02/23] KVM: x86: Kill last_tsc_{nsec,write,offset} fields David Woodhouse
2026-11-05 16:47 ` sashiko-bot
2026-11-05 15:49 ` [PATCH v11 03/23] KVM: x86: Replace nr_vcpus_matched_tsc count with all_vcpus_matched_tsc bool David Woodhouse
2026-11-05 15:49 ` [PATCH v11 04/23] KVM: x86: Allow KVM master clock mode when TSCs are offset from each other David Woodhouse
2026-11-05 17:27 ` sashiko-bot
2026-11-05 15:49 ` [PATCH v11 05/23] KVM: x86: Factor out kvm_use_master_clock() David Woodhouse
2026-11-05 15:49 ` [PATCH v11 06/23] KVM: x86: Avoid gratuitous global clock updates David Woodhouse
2026-11-05 15:49 ` [PATCH v11 07/23] KVM: x86: Avoid redundant masterclock updates from multiple vCPUs David Woodhouse
2026-11-05 18:00 ` sashiko-bot
2026-11-05 15:49 ` [PATCH v11 08/23] KVM: x86: Re-synchronize TSC after KVM_SET_TSC_KHZ David Woodhouse
2026-11-05 18:22 ` sashiko-bot
2026-11-05 15:49 ` [PATCH v11 09/23] KVM: x86: Cache host vclock_mode for masterclock eligibility checks David Woodhouse
2026-11-05 15:49 ` [PATCH v11 10/23] KVM: x86: Remove pvclock_gtod_data and private timekeeping code David Woodhouse
2026-11-05 15:49 ` [PATCH v11 11/23] KVM: x86: Activate master clock from kvm_arch_init_vm() David Woodhouse
2026-11-05 19:15 ` sashiko-bot
2026-11-05 15:49 ` [PATCH v11 12/23] UAPI: x86: Move pvclock-abi to UAPI for x86 platforms David Woodhouse
2026-11-05 15:49 ` [PATCH v11 13/23] KVM: selftests: Use UAPI pvclock-abi.h in xen_shinfo_test David Woodhouse
2026-11-05 15:49 ` [PATCH v11 14/23] KVM: x86: Add KVM_[GS]ET_CLOCK_GUEST for accurate KVM clock migration David Woodhouse
2026-11-05 19:51 ` sashiko-bot [this message]
2026-11-05 15:49 ` [PATCH v11 15/23] KVM: selftests: Add library helpers for clock and TSC-offset ioctls David Woodhouse
2026-11-05 15:49 ` [PATCH v11 16/23] KVM: x86: Add KVM_VCPU_TSC_SCALE and fix the documentation on TSC migration David Woodhouse
2026-11-05 20:06 ` sashiko-bot
2026-11-05 15:49 ` [PATCH v11 17/23] KVM: selftests: Add KVM/PV clock selftest to prove timer correction David Woodhouse
2026-11-05 15:49 ` [PATCH v11 18/23] KVM: selftests: Add master clock offset test David Woodhouse
2026-11-05 20:25 ` sashiko-bot
2026-11-05 15:49 ` [PATCH v11 19/23] KVM: selftests: Add Xen/generic CPUID timing leaf test David Woodhouse
2026-11-05 15:49 ` [PATCH v11 20/23] KVM: selftests: Add Xen runstate migration test David Woodhouse
2026-11-05 15:49 ` [PATCH v11 21/23] KVM: selftests: Test that the first user TSC write is honoured exactly David Woodhouse
2026-11-05 15:49 ` [PATCH v11 22/23] KVM: x86/xen: Drop open-coded fallback clock read in kvm_xen_start_timer() David Woodhouse
2026-11-05 15:49 ` [PATCH v11 23/23] KVM: x86: Use a single time snapshot in kvm_vm_ioctl_set_clock() David Woodhouse
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-200022@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=dwmw2@infradead.org \
--cc=kvm@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox