From: sashiko-bot@kernel.org
To: "Sean Christopherson" <seanjc@google.com>
Cc: kvm@vger.kernel.org
Subject: Re: [PATCH 4/4] KVM: x86/mmu: Use the generic auto-prefaulting to do prefetch for direct MMUs
Date: Wed, 28 Oct 2026 20:54:54 +0000 [thread overview]
Message-ID: <sashiko-outbox-188454@kernel.org> (raw)
In-Reply-To: <20261028202607.1844883-5-seanjc@google.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] kvm: x86/mmu: missing A/D-disabled check in kvm_mmu_auto_prefault() path
- [High] KVM: x86/mmu: auto-prefault invokes error PFN side effects in kvm_handle_error_pfn()
- [Medium] KVM: x86/mmu: byte size passed to find_last_bit() in kvm_mmu_auto_prefault()
- [Low] KVM: x86/mmu: lost PTE prefetching for nonpaging_page_fault() direct MMU
--
commit 75be7201988159bdb81f00fdc35e700a7ee104b2
Author: Sean Christopherson <seanjc@google.com>
KVM: x86/mmu: Use the generic auto-prefaulting to do prefetch for direct MMUs
This commit removes the custom PTE prefetching path for direct shadow MMUs in
direct_map() and enables generic auto-prefaulting via kvm_mmu_auto_prefault()
for all direct MMUs using kvm_tdp_page_fault().
> diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
> index c56a2eaa46463..7f8beb1b307bf 100644
> --- a/arch/x86/kvm/mmu/mmu.c
> +++ b/arch/x86/kvm/mmu/mmu.c
[ ... ]
> @@ -3301,33 +3301,6 @@ static void __direct_pte_prefetch(struct kvm_vcpu *vcpu,
> direct_pte_prefetch_many(vcpu, sp, start, spte);
> }
>
> -static void direct_pte_prefetch(struct kvm_vcpu *vcpu, u64 *sptep)
> -{
> - struct kvm_mmu_page *sp;
> -
> - sp = sptep_to_sp(sptep);
> -
> - /*
> - * Without accessed bits, there's no way to distinguish between
> - * actually accessed translations and prefetched, so disable pte
> - * prefetch if accessed bits aren't available.
> - */
> - if (sp_ad_disabled(sp))
> - return;
[Severity: High]
When hardware A/D bits are disabled (!kvm_ad_enabled), does switching from
direct_pte_prefetch() to kvm_mmu_auto_prefault() corrupt access tracking and
page aging on neighboring pages in the prefault window?
Previously, direct_pte_prefetch() checked sp_ad_disabled(sp) and skipped
prefetching when A/D bits were unavailable. With kvm_mmu_auto_prefault(), a
non-present or access-track page fault that returns RET_PF_FIXED triggers
auto-prefaulting across the aligned window via:
kvm_mmu_auto_prefault()
kvm_tdp_page_prefault(..., KVM_PREFETCH_AUTO, ...)
kvm_mmu_do_page_fault()
kvm_tdp_page_fault()
direct_page_fault() / kvm_tdp_mmu_page_fault()
fast_page_fault()
In page_fault_can_be_fast(), fault->prefetch is not checked:
if (!fault->present)
return !kvm_ad_enabled;
And in fast_page_fault(), any access-tracked SPTE in the prefault window
(whether installed by an earlier slow-path prefetch with
mark_spte_for_access_track() or aged by MMU notifiers via kvm_age_gfn()) has
its access permissions and shadow_accessed_mask restored:
if (unlikely(!kvm_ad_enabled) && is_access_track_spte(spte))
new_spte = restore_acc_track_spte(new_spte) |
shadow_accessed_mask;
This marks unaccessed neighboring pages as accessed, breaking kvm_age_gfn()
and kvm_test_age_gfn(). Should kvm_mmu_auto_prefault() bail out when
!kvm_ad_enabled (and should page_fault_can_be_fast() also reject
fault->prefetch when !fault->present)?
[ ... ]
> @@ -3594,13 +3567,8 @@ static int direct_map(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault)
> if (KVM_BUG_ON(it.level != fault->goal_level, vcpu->kvm))
> return -EIO;
>
> - ret = mmu_set_spte(vcpu, fault->slot, it.sptep, access,
> - base_gfn, fault->pfn, fault);
> - if (ret == RET_PF_SPURIOUS)
> - return ret;
> -
> - direct_pte_prefetch(vcpu, it.sptep);
> - return ret;
> + return mmu_set_spte(vcpu, fault->slot, it.sptep, access, base_gfn,
> + fault->pfn, fault);
> }
[Severity: Low]
Is it intentional that non-paging direct shadow MMUs (when !tdp_enabled and
the guest runs with CR0.PG = 0) lose PTE prefetching here?
When paging is disabled under shadow paging, nonpaging_init_context() sets
vcpu->arch.mmu->page_fault = nonpaging_page_fault with root_role.direct = 1,
which faults through:
nonpaging_page_fault()
direct_page_fault()
direct_map()
Removing direct_pte_prefetch() from direct_map() drops prefetching on this
path, while kvm_mmu_auto_prefault() and kvm_tdp_page_prefault() both check:
if (vcpu->arch.mmu->page_fault != kvm_tdp_page_fault)
and skip auto-prefaulting for nonpaging_page_fault().
> @@ -6685,7 +6653,7 @@ static void kvm_mmu_auto_prefault(struct kvm_vcpu *vcpu, gpa_t start,
> if (error_code & (PFERR_RSVD_MASK | PFERR_PRESENT_MASK))
> return;
>
> - if (!tdp_mmu_enabled || vcpu->arch.mmu->page_fault != kvm_tdp_page_fault)
> + if (vcpu->arch.mmu->page_fault != kvm_tdp_page_fault)
> return;
>
> nr_pages = min(nr_pages, KVM_PAGES_PER_HPAGE(PG_LEVEL_1G));
[Severity: High]
Can speculative prefaulting in kvm_mmu_auto_prefault() trigger unintended
fault-exit and signal side effects in kvm_handle_error_pfn() when a
neighboring unaccessed page cannot be resolved?
Unlike direct_pte_prefetch(), which used get_user_pages_fast_only() and
silently stopped on unmapped or poisoned neighboring pages,
kvm_mmu_auto_prefault() runs full fault handling with KVM_PREFETCH_AUTO
(fault->auto_prefetch = true) via:
kvm_mmu_auto_prefault()
kvm_tdp_page_prefault()
kvm_mmu_do_page_fault()
kvm_tdp_page_fault()
direct_page_fault() / kvm_tdp_mmu_page_fault()
kvm_mmu_faultin_pfn()
In __kvm_mmu_faultin_pfn(), if __kvm_faultin_pfn() with FOLL_NOWAIT returns
KVM_PFN_ERR_NEEDS_IO (for example, when a neighboring page is not yet faulted
into the host MM or is swapped out):
if (fault->pfn != KVM_PFN_ERR_NEEDS_IO || fault->auto_prefetch)
return RET_PF_CONTINUE;
this returns RET_PF_CONTINUE with fault->pfn still set to
KVM_PFN_ERR_NEEDS_IO, so kvm_mmu_faultin_pfn() sees is_error_pfn(fault->pfn)
and calls kvm_handle_error_pfn():
static int kvm_handle_error_pfn(struct kvm_vcpu *vcpu,
struct kvm_page_fault *fault)
{
if (is_sigpending_pfn(fault->pfn)) {
kvm_handle_signal_exit(vcpu);
return -EINTR;
}
...
if (fault->pfn == KVM_PFN_ERR_HWPOISON) {
kvm_send_hwpoison_signal(fault->slot, fault->gfn);
return RET_PF_RETRY;
}
kvm_mmu_prepare_memory_fault_exit(vcpu, fault);
return -EFAULT;
}
This leads to three issues on speculative prefaults:
1. For KVM_PFN_ERR_NEEDS_IO or KVM_PFN_ERR_FAULT (as well as private/shared
attribute mismatches or kvm_mmu_faultin_pfn_gmem() errors in
kvm_mmu_faultin_pfn()), kvm_mmu_prepare_memory_fault_exit() overwrites
vcpu->run->exit_reason = KVM_EXIT_MEMORY_FAULT and vcpu->run->memory_fault
for an unaccessed GPA while kvm_mmu_page_fault() still returns
RET_PF_FIXED (1) to vcpu_run().
2. If an unaccessed neighboring page is poisoned (KVM_PFN_ERR_HWPOISON),
kvm_handle_error_pfn() sends a synchronous BUS_MCEERR_AR SIGBUS to current
and returns RET_PF_RETRY. If SIGBUS is unblocked, userspace receives an
action-required MCE signal for a page the guest never touched; if SIGBUS is
masked or ignored, signal_pending(current) remains false and the retry loop
in kvm_tdp_page_prefault():
do {
...
r = kvm_mmu_do_page_fault(vcpu, gpa, error_code, prefetch, NULL,
level);
} while (r == RET_PF_RETRY);
spins indefinitely.
3. For KVM_PFN_ERR_SIGPENDING, kvm_handle_signal_exit() increments
vcpu->stat.signal_exits during the prefault before vcpu_run() handles the
pending signal and increments vcpu->stat.signal_exits a second time.
[Severity: Medium]
Also in kvm_mmu_auto_prefault(), right below this hunk:
nr_pages = min(nr_pages, KVM_PAGES_PER_HPAGE(PG_LEVEL_1G));
if (KVM_PAGES_PER_HPAGE(level) >= nr_pages)
return;
nr_pages_msb = find_last_bit((unsigned long *)&nr_pages, sizeof(nr_pages));
start = ALIGN_DOWN(start, gfn_to_gpa(BIT_ULL(nr_pages_msb)));
doesn't find_last_bit() expect its size argument in bits rather than bytes?
Because sizeof(nr_pages) is 8, find_last_bit() only inspects bits 0..7:
if (small_const_nbits(size)) {
unsigned long val = *addr & GENMASK(size - 1, 0);
return val ? __fls(val) : size;
}
When auto_prefault_nr_pages is set to 256 or larger (up to
KVM_PAGES_PER_HPAGE(PG_LEVEL_1G)), bits 8 and above are masked off, returning
8 when the low byte is zero (or a bit index in 0..7 if any low bit is set)
and misaligning start. In addition, removing !tdp_mmu_enabled exposes this
(unsigned long *)&nr_pages cast on CONFIG_X86_32 where unsigned long is 32
bits while gfn_t is 64 bits. Would fls64(nr_pages) - 1 (or __fls(nr_pages))
avoid both issues?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261028202607.1844883-1-seanjc@google.com?part=4
prev parent reply other threads:[~2026-10-28 20:54 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-28 20:26 [PATCH 0/4] KVM: x86/mmu: Add auto-prefetch to the TDP MMU Sean Christopherson
2026-10-28 20:26 ` [PATCH 1/4] KVM: x86/mmu: Rework gfn_to_memslot_dirty_bitmap() into auto-prefetch helper Sean Christopherson
2026-10-28 20:26 ` [PATCH 2/4] KVM: x86/mmu: Differentiate between manual and automatic prefetching Sean Christopherson
2026-10-28 20:26 ` [PATCH 3/4] KVM: x86/mmu: Add automatic prefaulting/prefetching for TDP MMU faults Sean Christopherson
2026-10-28 20:58 ` sashiko-bot
2026-10-28 21:51 ` James Houghton
2026-10-28 20:26 ` [PATCH 4/4] KVM: x86/mmu: Use the generic auto-prefaulting to do prefetch for direct MMUs Sean Christopherson
2026-10-28 20:54 ` sashiko-bot [this message]
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-188454@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=kvm@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=seanjc@google.com \
/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