Skip to content

Commit e25f0e0

Browse files
Ben Gardonbonzini
authored andcommitted
KVM: x86/mmu: Mark SPTEs in disconnected pages as removed
When clearing TDP MMU pages what have been disconnected from the paging structure root, set the SPTEs to a special non-present value which will not be overwritten by other threads. This is needed to prevent races in which a thread is clearing a disconnected page table, but another thread has already acquired a pointer to that memory and installs a mapping in an already cleared entry. This can lead to memory leaks and accounting errors. Reviewed-by: Peter Feiner <pfeiner@google.com> Signed-off-by: Ben Gardon <bgardon@google.com> Message-Id: <20210202185734.1680553-23-bgardon@google.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
1 parent 08f07c8 commit e25f0e0

File tree

1 file changed

+30
-6
lines changed

1 file changed

+30
-6
lines changed

arch/x86/kvm/mmu/tdp_mmu.c

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -334,9 +334,10 @@ static void handle_removed_tdp_mmu_page(struct kvm *kvm, u64 *pt,
334334
{
335335
struct kvm_mmu_page *sp = sptep_to_sp(pt);
336336
int level = sp->role.level;
337-
gfn_t gfn = sp->gfn;
337+
gfn_t base_gfn = sp->gfn;
338338
u64 old_child_spte;
339339
u64 *sptep;
340+
gfn_t gfn;
340341
int i;
341342

342343
trace_kvm_mmu_prepare_zap_page(sp);
@@ -345,16 +346,39 @@ static void handle_removed_tdp_mmu_page(struct kvm *kvm, u64 *pt,
345346

346347
for (i = 0; i < PT64_ENT_PER_PAGE; i++) {
347348
sptep = pt + i;
349+
gfn = base_gfn + (i * KVM_PAGES_PER_HPAGE(level - 1));
348350

349351
if (shared) {
350-
old_child_spte = xchg(sptep, 0);
352+
/*
353+
* Set the SPTE to a nonpresent value that other
354+
* threads will not overwrite. If the SPTE was
355+
* already marked as removed then another thread
356+
* handling a page fault could overwrite it, so
357+
* set the SPTE until it is set from some other
358+
* value to the removed SPTE value.
359+
*/
360+
for (;;) {
361+
old_child_spte = xchg(sptep, REMOVED_SPTE);
362+
if (!is_removed_spte(old_child_spte))
363+
break;
364+
cpu_relax();
365+
}
351366
} else {
352367
old_child_spte = READ_ONCE(*sptep);
353-
WRITE_ONCE(*sptep, 0);
368+
369+
/*
370+
* Marking the SPTE as a removed SPTE is not
371+
* strictly necessary here as the MMU lock will
372+
* stop other threads from concurrently modifying
373+
* this SPTE. Using the removed SPTE value keeps
374+
* the two branches consistent and simplifies
375+
* the function.
376+
*/
377+
WRITE_ONCE(*sptep, REMOVED_SPTE);
354378
}
355-
handle_changed_spte(kvm, kvm_mmu_page_as_id(sp),
356-
gfn + (i * KVM_PAGES_PER_HPAGE(level - 1)),
357-
old_child_spte, 0, level - 1, shared);
379+
handle_changed_spte(kvm, kvm_mmu_page_as_id(sp), gfn,
380+
old_child_spte, REMOVED_SPTE, level - 1,
381+
shared);
358382
}
359383

360384
kvm_flush_remote_tlbs_with_address(kvm, gfn,

0 commit comments

Comments
 (0)