From da260bcb56feeb6489a83f310308453a0a7de25e Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Mon, 31 Aug 2026 22:26:43 +0100 Subject: KVM: pfncache: use a dedicated invalidation sequence for cache refresh The gfn_to_pfn_cache refresh path guards against mmu notifier invalidations which complete while it has dropped gpc->lock for the HVA->PFN lookup: hva_to_pfn_retry() samples kvm->mmu_invalidate_seq and retries if it changed, or if mn_active_invalidate_count is still elevated. That is insufficient for HVA-based caches. mmu_invalidate_seq is only advanced by kvm_mmu_invalidate_end() when the invalidated range overlaps a memslot, and an HVA-based cache (e.g. the Xen shared_info page mapped with KVM_XEN_ATTR_TYPE_SHARED_INFO_HVA) need not be backed by any memslot at all. An invalidation of the cached HVA which starts and ends entirely within the lookup window is thus invisible to the retry check: mn_active_invalidate_count is back to zero and the sequence never moved. The refresh then publishes a mapping of a page which has already been freed, and the next reader dereferences it: BUG: KASAN: use-after-free in kvm_xen_shared_info_init+0x3c6/0x440 Read of size 4 at addr ffff8880599c2900 by task syz.2.383/7257 Since gfn_to_pfn_cache_invalidate_start() deliberately skips caches which are not currently valid (including one whose refresh is in progress, as the refresh clears the valid flag before dropping the lock), the retry check is the only line of defence, and it must fire for *any* invalidation, not just those hitting a memslot. Add a dedicated kvm->gpc_invalidate_seq, incremented by every kvm_mmu_notifier_invalidate_range_end() under mn_invalidate_lock before mn_active_invalidate_count is decremented, and check it in hva_to_pfn_retry() instead of mmu_invalidate_seq. Incrementing in range_end() in the same critical section as the in-progress count also closes the variant where the cache is activated with the contested HVA only after invalidate_range_start() has run. The same bug is also reachable through the per-vCPU vcpu_info cache (KVM_XEN_VCPU_ATTR_TYPE_VCPU_INFO_HVA), where the stale mapping is then dereferenced by kvm_setup_guest_pvclock() on the next KVM_RUN: BUG: KASAN: use-after-free in kvm_setup_guest_pvclock+0x5bf/0x660 This intentionally makes refresh retry on *unrelated* mmu notifier events; restoring precision (and reworking the GPC locking more generally) is left for a subsequent series. Reproducers: https://david.woodhou.se/xen_shinfo_race.c https://david.woodhou.se/vcpu_info_race.c Suggested-by: Sean Christopherson Reported-by: syzbot+0948c82180d475ad24e2@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6a0c5f2c.a00a0220.2c7954.0000.GAE@google.com/ Tested-by: syzbot+0948c82180d475ad24e2@syzkaller.appspotmail.com Reported-by: syzbot+fb7c2dd166d3ea63df2a@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6a426dd2.854d4ab9.360e1d.0008.GAE@google.com/ Fixes: b9220d32799a ("KVM: x86/xen: allow shared_info to be mapped by fixed HVA") Cc: stable@vger.kernel.org Signed-off-by: David Woodhouse Assisted-by: Claude:claude-mythos-5 Reviewed-by: Paul Durrant Link: https://patch.msgid.link/20260831213632.81023-13-dwmw2@infradead.org Signed-off-by: Paolo Bonzini --- include/linux/kvm_host.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'include/linux') diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index 03bfc92864b6..3dd04605f2e5 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -855,6 +855,8 @@ struct kvm { gfn_t mmu_invalidate_range_start; gfn_t mmu_invalidate_range_end; + unsigned long gpc_invalidate_seq; + struct list_head devices; u64 manual_dirty_log_protect; struct dentry *debugfs_dentry; -- cgit v1.2.3