From: David Woodhouse <[email protected]> The physical addresses which nested page setup latches into hardware control structures (vmcs02's APIC-access, virtual-APIC and posted interrupt descriptor addresses) are derived from gPA→uHVA translations which a memslot update can change. Software readers of the underlying gfn_to_pfn_caches catch this lazily, via the memslot generation check in kvm_gpc_check() at their next use — but the CPU's use of a latched address from guest mode is continuous and checks nothing. A vCPU running L2 across a memslot move would keep using the old translation until something forced it to re-resolve; L0 exits which re-enter L2 without a nested VM-exit never re-run nested page setup.
(This is a staleness, not a lifetime, problem: freeing the underlying page is the mmu_notifier's business and that path kicks pinned vCPUs synchronously. The replaced kvm_host_map code had the same staleness with no remedy at all.) The alternative, checking each cache's memslot generation in the VM-entry path after vcpu->mode is set, is strictly worse: the check would run on every nested VM-entry forever, in a context which cannot refresh (IRQs off), so its only possible action on a mismatch would be to post KVM_REQ_GET_NESTED_STATE_PAGES and bail for the refresh to happen outside. Posting that same request from the memslot update itself — the single point where the generation actually changes, and a slow path by definition — is the same mechanism minus the per-entry cost. The request bit is also the artifact that survives racing with a concurrent VM-entry: a bare kick landing before vcpu->mode is set would be lost, and a vCPU which resolved its pages against the old memslots but has not yet entered guest mode is invisible to any is_guest_mode() filter, so the request is posted unconditionally to every vCPU. Accordingly, downgrade the WARN in svm_get_nested_state_pages(): a spurious request outside guest mode is now expected, and a no-op. (vmx_get_nested_state_pages already tolerates it.) The memslot-move mode of the vmx_apic_update_test selftest exercises this path. Signed-off-by: David Woodhouse <[email protected]> Assisted-by: Claude:claude-mythos-5 --- arch/x86/kvm/svm/nested.c | 7 ++++++- arch/x86/kvm/x86.c | 26 ++++++++++++++++++++------ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c index 73f37b050d0a..acc423b13445 100644 --- a/arch/x86/kvm/svm/nested.c +++ b/arch/x86/kvm/svm/nested.c @@ -2106,7 +2106,12 @@ static int svm_set_nested_state(struct kvm_vcpu *vcpu, static bool svm_get_nested_state_pages(struct kvm_vcpu *vcpu) { - if (WARN_ON(!is_guest_mode(vcpu))) + /* + * Memslot updates post this request to every vCPU (to make any + * vCPU which has guest pages latched re-resolve them against the + * new memslots), so it can arrive with nothing to do. + */ + if (!is_guest_mode(vcpu)) return true; if (is_pae_paging(vcpu)) { diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index 116932e13d59..07d1cfb051f5 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -10172,18 +10172,32 @@ static int kvm_alloc_memslot_metadata(struct kvm *kvm, void kvm_arch_memslots_updated(struct kvm *kvm, u64 gen) { - struct kvm_vcpu *vcpu; - unsigned long i; - /* * memslots->generation has been incremented. * mmio generation may have reached its maximum value. */ kvm_mmu_invalidate_mmio_sptes(kvm, gen); - /* Force re-initialization of steal_time cache */ - kvm_for_each_vcpu(i, vcpu, kvm) - kvm_vcpu_kick(vcpu); + /* + * Force re-initialization of the steal_time cache, and of any + * nested-state pages whose physical addresses a vCPU has latched + * in hardware control structures (e.g. vmcs02) from a + * gfn_to_pfn_cache. Software readers of such caches catch the + * generation bump lazily, via kvm_gpc_check() at their next use; + * the CPU's use from guest mode is continuous and checks nothing, + * so the vCPU must be told to re-resolve and re-latch before it + * next enters the guest. The request is the artifact that + * survives racing with a concurrent VM-entry (a bare kick landing + * before vcpu->mode is set would be lost); its handler re-runs + * nested page setup, whose gPA lookups then see the new + * generation and refresh. + * + * The wake/kick this performs on every vCPU is also what forces + * re-initialization of the steal_time cache: its check-at-use + * sites likewise only see the new generation once the vCPU goes + * around its run loop. + */ + kvm_make_all_cpus_request(kvm, KVM_REQ_GET_NESTED_STATE_PAGES); } int kvm_arch_prepare_memory_region(struct kvm *kvm, -- 2.55.0

