Message ID | 20230718234512.1690985-2-seanjc@google.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | KVM: guest_memfd() and per-page attributes | expand |
On Wed Jul 19, 2023 at 2:44 AM EEST, Sean Christopherson wrote: > /* Huge pages aren't expected to be modified without first being zapped. */ > - WARN_ON(pte_huge(range->pte) || range->start + 1 != range->end); > + WARN_ON(pte_huge(range->arg.pte) || range->start + 1 != range->end); Not familiar with this code. Just checking whether whether instead pr_{warn,err}() combined with return false would be a more graceful option? BR, Jarkko
On Wed, Jul 19, 2023, Jarkko Sakkinen wrote: > On Wed Jul 19, 2023 at 2:44 AM EEST, Sean Christopherson wrote: > > /* Huge pages aren't expected to be modified without first being zapped. */ > > - WARN_ON(pte_huge(range->pte) || range->start + 1 != range->end); > > + WARN_ON(pte_huge(range->arg.pte) || range->start + 1 != range->end); > > Not familiar with this code. Just checking whether whether instead > pr_{warn,err}() The "full" WARN is desirable, this is effecitvely an assert on the contract between the primary MMU, generic KVM code, and x86's TDP MMU. The .change_pte() mmu_notifier callback doesn't allow for hugepages, i.e. it's a (likely fatal) kernel bug if a hugepage is encountered at this point. Ditto for the "start + 1 == end" check, if that fails then generic KVM likely has a fatal bug. > combined with return false would be a more graceful option? The return value communicates whether or not a TLB flush is needed, not whether or not the operation was successful, i.e. there is no way to cancel the unexpected PTE change.
On 7/19/23 01:44, Sean Christopherson wrote: > + BUILD_BUG_ON(sizeof(gfn_range.arg) != sizeof(gfn_range.arg.raw)); > + BUILD_BUG_ON(sizeof(range->arg) != sizeof(range->arg.raw)); I think these should be static assertions near the definition of the structs. However another possibility is to remove 'raw' and just assign the whole union. Apart from this, Reviewed-by: Paolo Bonzini <pbonzini@redhat.com> Paolo > + BUILD_BUG_ON(sizeof(gfn_range.arg) != sizeof(range->arg));
On Tue, Jul 18, 2023 at 04:44:44PM -0700, Sean Christopherson wrote: May I know why KVM now needs to register to callback .change_pte()? As also commented in kvm_mmu_notifier_change_pte(), .change_pte() must be surrounded by .invalidate_range_{start,end}(). While kvm_mmu_notifier_invalidate_range_start() has called kvm_unmap_gfn_range() to zap all leaf SPTEs, and page fault path will not install new SPTEs successfully before kvm_mmu_notifier_invalidate_range_end(), kvm_set_spte_gfn() should not be able to find any shadow present leaf entries to update PFN. Or could we just delete completely "kvm_handle_hva_range(mn, address, address + 1, pte, kvm_set_spte_gfn);" from kvm_mmu_notifier_change_pte() ? > diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c > index 6db9ef288ec3..55f03a68f1cd 100644 > --- a/arch/arm64/kvm/mmu.c > +++ b/arch/arm64/kvm/mmu.c > @@ -1721,7 +1721,7 @@ bool kvm_unmap_gfn_range(struct kvm *kvm, struct kvm_gfn_range *range) > > bool kvm_set_spte_gfn(struct kvm *kvm, struct kvm_gfn_range *range) > { > - kvm_pfn_t pfn = pte_pfn(range->pte); > + kvm_pfn_t pfn = pte_pfn(range->arg.pte); > > if (!kvm->arch.mmu.pgt) > return false; > diff --git a/arch/mips/kvm/mmu.c b/arch/mips/kvm/mmu.c > index e8c08988ed37..7b2ac1319d70 100644 > --- a/arch/mips/kvm/mmu.c > +++ b/arch/mips/kvm/mmu.c > @@ -447,7 +447,7 @@ bool kvm_unmap_gfn_range(struct kvm *kvm, struct kvm_gfn_range *range) > bool kvm_set_spte_gfn(struct kvm *kvm, struct kvm_gfn_range *range) > { > gpa_t gpa = range->start << PAGE_SHIFT; > - pte_t hva_pte = range->pte; > + pte_t hva_pte = range->arg.pte; > pte_t *gpa_pte = kvm_mips_pte_for_gpa(kvm, NULL, gpa); > pte_t old_pte; > > diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c > index f2eb47925806..857f4312b0f8 100644 > --- a/arch/riscv/kvm/mmu.c > +++ b/arch/riscv/kvm/mmu.c > @@ -559,7 +559,7 @@ bool kvm_unmap_gfn_range(struct kvm *kvm, struct kvm_gfn_range *range) > bool kvm_set_spte_gfn(struct kvm *kvm, struct kvm_gfn_range *range) > { > int ret; > - kvm_pfn_t pfn = pte_pfn(range->pte); > + kvm_pfn_t pfn = pte_pfn(range->arg.pte); > > if (!kvm->arch.pgd) > return false; > diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c > index ec169f5c7dce..d72f2b20f430 100644 > --- a/arch/x86/kvm/mmu/mmu.c > +++ b/arch/x86/kvm/mmu/mmu.c > @@ -1588,7 +1588,7 @@ static __always_inline bool kvm_handle_gfn_range(struct kvm *kvm, > for_each_slot_rmap_range(range->slot, PG_LEVEL_4K, KVM_MAX_HUGEPAGE_LEVEL, > range->start, range->end - 1, &iterator) > ret |= handler(kvm, iterator.rmap, range->slot, iterator.gfn, > - iterator.level, range->pte); > + iterator.level, range->arg.pte); > > return ret; > } > diff --git a/arch/x86/kvm/mmu/tdp_mmu.c b/arch/x86/kvm/mmu/tdp_mmu.c > index 512163d52194..6250bd3d20c1 100644 > --- a/arch/x86/kvm/mmu/tdp_mmu.c > +++ b/arch/x86/kvm/mmu/tdp_mmu.c > @@ -1241,7 +1241,7 @@ static bool set_spte_gfn(struct kvm *kvm, struct tdp_iter *iter, > u64 new_spte; > > /* Huge pages aren't expected to be modified without first being zapped. */ > - WARN_ON(pte_huge(range->pte) || range->start + 1 != range->end); > + WARN_ON(pte_huge(range->arg.pte) || range->start + 1 != range->end); > > if (iter->level != PG_LEVEL_4K || > !is_shadow_present_pte(iter->old_spte)) > @@ -1255,9 +1255,9 @@ static bool set_spte_gfn(struct kvm *kvm, struct tdp_iter *iter, > */ > tdp_mmu_iter_set_spte(kvm, iter, 0); > > - if (!pte_write(range->pte)) { > + if (!pte_write(range->arg.pte)) { > new_spte = kvm_mmu_changed_pte_notifier_make_spte(iter->old_spte, > - pte_pfn(range->pte)); > + pte_pfn(range->arg.pte)); > > tdp_mmu_iter_set_spte(kvm, iter, new_spte); > }
On 2023-07-21 at 14:26:11 +0800, Yan Zhao wrote: > On Tue, Jul 18, 2023 at 04:44:44PM -0700, Sean Christopherson wrote: > > May I know why KVM now needs to register to callback .change_pte()? I can see the original purpose is to "setting a pte in the shadow page table directly, instead of flushing the shadow page table entry and then getting vmexit to set it"[1]. IIUC, KVM is expected to directly make the new pte present for new pages in this callback, like for COW. > As also commented in kvm_mmu_notifier_change_pte(), .change_pte() must be > surrounded by .invalidate_range_{start,end}(). > > While kvm_mmu_notifier_invalidate_range_start() has called kvm_unmap_gfn_range() > to zap all leaf SPTEs, and page fault path will not install new SPTEs > successfully before kvm_mmu_notifier_invalidate_range_end(), > kvm_set_spte_gfn() should not be able to find any shadow present leaf entries to > update PFN. I also failed to figure out how the kvm_set_spte_gfn() could pass several !is_shadow_present_pte(iter.old_spte) check then write the new pte. [1] https://lore.kernel.org/all/200909222039.n8MKd4TL002696@imap1.linux-foundation.org/ Thanks, Yilun > > Or could we just delete completely > "kvm_handle_hva_range(mn, address, address + 1, pte, kvm_set_spte_gfn);" > from kvm_mmu_notifier_change_pte() ?
On Fri, Jul 21, 2023, Xu Yilun wrote: > On 2023-07-21 at 14:26:11 +0800, Yan Zhao wrote: > > On Tue, Jul 18, 2023 at 04:44:44PM -0700, Sean Christopherson wrote: > > > > May I know why KVM now needs to register to callback .change_pte()? > > I can see the original purpose is to "setting a pte in the shadow page > table directly, instead of flushing the shadow page table entry and then > getting vmexit to set it"[1]. > > IIUC, KVM is expected to directly make the new pte present for new > pages in this callback, like for COW. Yes. > > As also commented in kvm_mmu_notifier_change_pte(), .change_pte() must be > > surrounded by .invalidate_range_{start,end}(). > > > > While kvm_mmu_notifier_invalidate_range_start() has called kvm_unmap_gfn_range() > > to zap all leaf SPTEs, and page fault path will not install new SPTEs > > successfully before kvm_mmu_notifier_invalidate_range_end(), > > kvm_set_spte_gfn() should not be able to find any shadow present leaf entries to > > update PFN. > > I also failed to figure out how the kvm_set_spte_gfn() could pass > several !is_shadow_present_pte(iter.old_spte) check then write the new > pte. It can't. .change_pte() has been dead code on x86 for 10+ years at this point, and if my assessment from a few years back still holds true, it's dead code on all architectures. The only reason I haven't formally proposed dropping the hook is that I don't want to risk the patch backfiring, i.e. I don't want to prompt someone to care enough to try and fix it. commit c13fda237f08a388ba8a0849785045944bf39834 Author: Sean Christopherson <seanjc@google.com> Date: Fri Apr 2 02:56:49 2021 +0200 KVM: Assert that notifier count is elevated in .change_pte() In KVM's .change_pte() notification callback, replace the notifier sequence bump with a WARN_ON assertion that the notifier count is elevated. An elevated count provides stricter protections than bumping the sequence, and the sequence is guarnateed to be bumped before the count hits zero. When .change_pte() was added by commit 828502d30073 ("ksm: add mmu_notifier set_pte_at_notify()"), bumping the sequence was necessary as .change_pte() would be invoked without any surrounding notifications. However, since commit 6bdb913f0a70 ("mm: wrap calls to set_pte_at_notify with invalidate_range_start and invalidate_range_end"), all calls to .change_pte() are guaranteed to be surrounded by start() and end(), and so are guaranteed to run with an elevated notifier count. Note, wrapping .change_pte() with .invalidate_range_{start,end}() is a bug of sorts, as invalidating the secondary MMU's (KVM's) PTE defeats the purpose of .change_pte(). Every arch's kvm_set_spte_hva() assumes .change_pte() is called when the relevant SPTE is present in KVM's MMU, as the original goal was to accelerate Kernel Samepage Merging (KSM) by updating KVM's SPTEs without requiring a VM-Exit (due to invalidating the SPTE). I.e. it means that .change_pte() is effectively dead code on _all_ architectures. x86 and MIPS are clearcut nops if the old SPTE is not-present, and that is guaranteed due to the prior invalidation. PPC simply unmaps the SPTE, which again should be a nop due to the invalidation. arm64 is a bit murky, but it's also likely a nop because kvm_pgtable_stage2_map() is called without a cache pointer, which means it will map an entry if and only if an existing PTE was found. For now, take advantage of the bug to simplify future consolidation of KVMs's MMU notifier code. Doing so will not greatly complicate fixing .change_pte(), assuming it's even worth fixing. .change_pte() has been broken for 8+ years and no one has complained. Even if there are KSM+KVM users that care deeply about its performance, the benefits of avoiding VM-Exits via .change_pte() need to be reevaluated to justify the added complexity and testing burden. Ripping out .change_pte() entirely would be a lot easier.
On Wed, Jul 19, 2023, Paolo Bonzini wrote: > On 7/19/23 01:44, Sean Christopherson wrote: > > + BUILD_BUG_ON(sizeof(gfn_range.arg) != sizeof(gfn_range.arg.raw)); > > + BUILD_BUG_ON(sizeof(range->arg) != sizeof(range->arg.raw)); > > I think these should be static assertions near the definition of the > structs. However another possibility is to remove 'raw' and just assign the > whole union. Duh, and use a named union. I think when I first proposed this I forgot that a single value would be passed between kvm_hva_range *and* kvm_gfn_range, and so created an anonymous union without thinking about the impliciations. A named union is _much_ cleaner. I'll post a complete version of the below snippet as a standalone non-RFC patch. diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index 9d3ac7720da9..9125d0ab642d 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -256,11 +256,15 @@ int kvm_async_pf_wakeup_all(struct kvm_vcpu *vcpu); #endif #ifdef KVM_ARCH_WANT_MMU_NOTIFIER +union kvm_mmu_notifier_arg { + pte_t pte; +}; + struct kvm_gfn_range { struct kvm_memory_slot *slot; gfn_t start; gfn_t end; - pte_t pte; + union kvm_mmu_notifier_arg arg; bool may_block; }; bool kvm_unmap_gfn_range(struct kvm *kvm, struct kvm_gfn_range *range); diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index dfbaafbe3a00..f84ef9399aee 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -526,7 +526,7 @@ typedef void (*on_unlock_fn_t)(struct kvm *kvm); struct kvm_hva_range { unsigned long start; unsigned long end; - pte_t pte; + union kvm_mmu_notifier_arg arg; hva_handler_t handler; on_lock_fn_t on_lock; on_unlock_fn_t on_unlock; @@ -547,6 +547,8 @@ static void kvm_null_fn(void) } #define IS_KVM_NULL_FN(fn) ((fn) == (void *)kvm_null_fn) +static const union kvm_mmu_notifier_arg KVM_NO_ARG; + /* Iterate over each memslot intersecting [start, last] (inclusive) range */ #define kvm_for_each_memslot_in_hva_range(node, slots, start, last) \ for (node = interval_tree_iter_first(&slots->hva_tree, start, last); \ @@ -591,7 +593,7 @@ static __always_inline int __kvm_handle_hva_range(struct kvm *kvm, * bother making these conditional (to avoid writes on * the second or later invocation of the handler). */ - gfn_range.pte = range->pte; + gfn_range.arg = range->arg; gfn_range.may_block = range->may_block; /*
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c index 6db9ef288ec3..55f03a68f1cd 100644 --- a/arch/arm64/kvm/mmu.c +++ b/arch/arm64/kvm/mmu.c @@ -1721,7 +1721,7 @@ bool kvm_unmap_gfn_range(struct kvm *kvm, struct kvm_gfn_range *range) bool kvm_set_spte_gfn(struct kvm *kvm, struct kvm_gfn_range *range) { - kvm_pfn_t pfn = pte_pfn(range->pte); + kvm_pfn_t pfn = pte_pfn(range->arg.pte); if (!kvm->arch.mmu.pgt) return false; diff --git a/arch/mips/kvm/mmu.c b/arch/mips/kvm/mmu.c index e8c08988ed37..7b2ac1319d70 100644 --- a/arch/mips/kvm/mmu.c +++ b/arch/mips/kvm/mmu.c @@ -447,7 +447,7 @@ bool kvm_unmap_gfn_range(struct kvm *kvm, struct kvm_gfn_range *range) bool kvm_set_spte_gfn(struct kvm *kvm, struct kvm_gfn_range *range) { gpa_t gpa = range->start << PAGE_SHIFT; - pte_t hva_pte = range->pte; + pte_t hva_pte = range->arg.pte; pte_t *gpa_pte = kvm_mips_pte_for_gpa(kvm, NULL, gpa); pte_t old_pte; diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c index f2eb47925806..857f4312b0f8 100644 --- a/arch/riscv/kvm/mmu.c +++ b/arch/riscv/kvm/mmu.c @@ -559,7 +559,7 @@ bool kvm_unmap_gfn_range(struct kvm *kvm, struct kvm_gfn_range *range) bool kvm_set_spte_gfn(struct kvm *kvm, struct kvm_gfn_range *range) { int ret; - kvm_pfn_t pfn = pte_pfn(range->pte); + kvm_pfn_t pfn = pte_pfn(range->arg.pte); if (!kvm->arch.pgd) return false; diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c index ec169f5c7dce..d72f2b20f430 100644 --- a/arch/x86/kvm/mmu/mmu.c +++ b/arch/x86/kvm/mmu/mmu.c @@ -1588,7 +1588,7 @@ static __always_inline bool kvm_handle_gfn_range(struct kvm *kvm, for_each_slot_rmap_range(range->slot, PG_LEVEL_4K, KVM_MAX_HUGEPAGE_LEVEL, range->start, range->end - 1, &iterator) ret |= handler(kvm, iterator.rmap, range->slot, iterator.gfn, - iterator.level, range->pte); + iterator.level, range->arg.pte); return ret; } diff --git a/arch/x86/kvm/mmu/tdp_mmu.c b/arch/x86/kvm/mmu/tdp_mmu.c index 512163d52194..6250bd3d20c1 100644 --- a/arch/x86/kvm/mmu/tdp_mmu.c +++ b/arch/x86/kvm/mmu/tdp_mmu.c @@ -1241,7 +1241,7 @@ static bool set_spte_gfn(struct kvm *kvm, struct tdp_iter *iter, u64 new_spte; /* Huge pages aren't expected to be modified without first being zapped. */ - WARN_ON(pte_huge(range->pte) || range->start + 1 != range->end); + WARN_ON(pte_huge(range->arg.pte) || range->start + 1 != range->end); if (iter->level != PG_LEVEL_4K || !is_shadow_present_pte(iter->old_spte)) @@ -1255,9 +1255,9 @@ static bool set_spte_gfn(struct kvm *kvm, struct tdp_iter *iter, */ tdp_mmu_iter_set_spte(kvm, iter, 0); - if (!pte_write(range->pte)) { + if (!pte_write(range->arg.pte)) { new_spte = kvm_mmu_changed_pte_notifier_make_spte(iter->old_spte, - pte_pfn(range->pte)); + pte_pfn(range->arg.pte)); tdp_mmu_iter_set_spte(kvm, iter, new_spte); } diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index 9d3ac7720da9..b901571ab61e 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -260,7 +260,10 @@ struct kvm_gfn_range { struct kvm_memory_slot *slot; gfn_t start; gfn_t end; - pte_t pte; + union { + pte_t pte; + u64 raw; + } arg; bool may_block; }; bool kvm_unmap_gfn_range(struct kvm *kvm, struct kvm_gfn_range *range); diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index dfbaafbe3a00..d58b7a506d27 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -526,7 +526,10 @@ typedef void (*on_unlock_fn_t)(struct kvm *kvm); struct kvm_hva_range { unsigned long start; unsigned long end; - pte_t pte; + union { + pte_t pte; + u64 raw; + } arg; hva_handler_t handler; on_lock_fn_t on_lock; on_unlock_fn_t on_unlock; @@ -562,6 +565,10 @@ static __always_inline int __kvm_handle_hva_range(struct kvm *kvm, struct kvm_memslots *slots; int i, idx; + BUILD_BUG_ON(sizeof(gfn_range.arg) != sizeof(gfn_range.arg.raw)); + BUILD_BUG_ON(sizeof(range->arg) != sizeof(range->arg.raw)); + BUILD_BUG_ON(sizeof(gfn_range.arg) != sizeof(range->arg)); + if (WARN_ON_ONCE(range->end <= range->start)) return 0; @@ -591,7 +598,7 @@ static __always_inline int __kvm_handle_hva_range(struct kvm *kvm, * bother making these conditional (to avoid writes on * the second or later invocation of the handler). */ - gfn_range.pte = range->pte; + gfn_range.arg.raw = range->arg.raw; gfn_range.may_block = range->may_block; /* @@ -639,7 +646,7 @@ static __always_inline int kvm_handle_hva_range(struct mmu_notifier *mn, const struct kvm_hva_range range = { .start = start, .end = end, - .pte = pte, + .arg.pte = pte, .handler = handler, .on_lock = (void *)kvm_null_fn, .on_unlock = (void *)kvm_null_fn, @@ -659,7 +666,6 @@ static __always_inline int kvm_handle_hva_range_no_flush(struct mmu_notifier *mn const struct kvm_hva_range range = { .start = start, .end = end, - .pte = __pte(0), .handler = handler, .on_lock = (void *)kvm_null_fn, .on_unlock = (void *)kvm_null_fn, @@ -747,7 +753,6 @@ static int kvm_mmu_notifier_invalidate_range_start(struct mmu_notifier *mn, const struct kvm_hva_range hva_range = { .start = range->start, .end = range->end, - .pte = __pte(0), .handler = kvm_unmap_gfn_range, .on_lock = kvm_mmu_invalidate_begin, .on_unlock = kvm_arch_guest_memory_reclaimed, @@ -812,7 +817,6 @@ static void kvm_mmu_notifier_invalidate_range_end(struct mmu_notifier *mn, const struct kvm_hva_range hva_range = { .start = range->start, .end = range->end, - .pte = __pte(0), .handler = (void *)kvm_null_fn, .on_lock = kvm_mmu_invalidate_end, .on_unlock = (void *)kvm_null_fn,
Signed-off-by: Sean Christopherson <seanjc@google.com> --- arch/arm64/kvm/mmu.c | 2 +- arch/mips/kvm/mmu.c | 2 +- arch/riscv/kvm/mmu.c | 2 +- arch/x86/kvm/mmu/mmu.c | 2 +- arch/x86/kvm/mmu/tdp_mmu.c | 6 +++--- include/linux/kvm_host.h | 5 ++++- virt/kvm/kvm_main.c | 16 ++++++++++------ 7 files changed, 21 insertions(+), 14 deletions(-)