Fix a race in pmap_emulate_modified().

pmap_emulate_modify() was assuming that no changes to the pmap could
take place between the TLB signaling the fault and
pmap_emulate_modify()'s acquisition of the pmap lock, but that's clearly
not even true in the uniprocessor case, nevermind the SMP case.

Submitted by:	Nathaniel Filardo <nwf20@cl.cam.ac.uk>
Reviewed by:	kib
MFC after:	2 weeks
Differential Revision:	https://reviews.freebsd.org/D24523
This commit is contained in:
Mark Johnston 2020-04-24 21:21:49 +00:00
parent 117d10f3c1
commit 06459adc89
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=360281

View File

@ -3502,28 +3502,71 @@ pmap_emulate_modified(pmap_t pmap, vm_offset_t va)
PMAP_LOCK(pmap);
pte = pmap_pte(pmap, va);
if (pte == NULL)
panic("pmap_emulate_modified: can't find PTE");
#ifdef SMP
/* It is possible that some other CPU changed m-bit */
if (!pte_test(pte, PTE_V) || pte_test(pte, PTE_D)) {
/*
* It is possible that some other CPU or thread changed the pmap while
* we weren't looking; in the SMP case, this is readily apparent, but
* it can even happen in the UP case, because we may have been blocked
* on PMAP_LOCK(pmap) above while someone changed this out from
* underneath us.
*/
if (pte == NULL) {
/*
* This PTE's PTP (or one of its ancestors) has been reclaimed;
* trigger a full fault to reconstruct it via pmap_enter.
*/
PMAP_UNLOCK(pmap);
return (1);
}
if (!pte_test(pte, PTE_V)) {
/*
* This PTE is no longer valid; the other thread or other
* processor must have arranged for our TLB to no longer
* have this entry, possibly by IPI, so no tlb_update is
* required. Fall out of the fast path and go take a
* general fault before retrying the instruction (or taking
* a signal).
*/
PMAP_UNLOCK(pmap);
return (1);
}
if (pte_test(pte, PTE_D)) {
/*
* This PTE is valid and has the PTE_D bit asserted; since
* this is an increase in permission, we may have been expected
* to update the TLB lazily. Do so here and return, on the
* fast path, to retry the instruction.
*/
tlb_update(pmap, va, *pte);
PMAP_UNLOCK(pmap);
return (0);
}
#else
if (!pte_test(pte, PTE_V) || pte_test(pte, PTE_D))
panic("pmap_emulate_modified: invalid pte");
#endif
if (pte_test(pte, PTE_RO)) {
/*
* This PTE is valid, not dirty, and read-only. Go take a
* full fault (most likely to upgrade this part of the address
* space to writeable).
*/
PMAP_UNLOCK(pmap);
return (1);
}
pte_set(pte, PTE_D);
tlb_update(pmap, va, *pte);
if (!pte_test(pte, PTE_MANAGED))
panic("pmap_emulate_modified: unmanaged page");
/*
* PTE is valid, managed, not dirty, and not read-only. Set PTE_D
* and eagerly update the local TLB, returning on the fast path.
*/
pte_set(pte, PTE_D);
tlb_update(pmap, va, *pte);
PMAP_UNLOCK(pmap);
return (0);
}