DF-5244 / fix.diff
--- a/sys/platform/pc64/x86_64/pmap.c +++ b/sys/platform/pc64/x86_64/pmap.c @@ -5596,10 +5596,33 @@ } ptep = pmap_pte_quick(pmap, va); if (pmap_pte_v(pmap, ptep) && pmap_pte_w(pmap, ptep)) { - atomic_add_long(&pmap->pm_stats.wired_count, -1); - atomic_clear_long(ptep, pmap->pmap_bits[PG_W_IDX]); - pa = *ptep & PG_FRAME; - m = PHYS_TO_VM_PAGE(pa); + /* + * DF-5244: atomically clear PG_W and capture the page + * frame from the same PTE we won the race on. The old + * clear-then-reread sequence raced cmpset-based PTE + * teardown from the PMAP_PAGE_BACKING_SCAN paths + * (pmap_remove_all/pmap_remove_specific), which take + * no pt_pv lock and no placemarker: it could read a + * zeroed PTE (PHYS_TO_VM_PAGE(0) is out of bounds) or + * race the other unwire into a double + * vm_page_unwire(). + */ + pa = 0; + for (;;) { + pt_entry_t opte = *ptep; + cpu_ccfence(); + if ((opte & pmap->pmap_bits[PG_W_IDX]) == 0) + break; + if (atomic_cmpset_long(ptep, opte, + opte & ~pmap->pmap_bits[PG_W_IDX])) { + pa = opte & PG_FRAME; + break; + } + } + if (pa) { + atomic_add_long(&pmap->pm_stats.wired_count, -1); + m = PHYS_TO_VM_PAGE(pa); + } /* caller handles m->wire_count */ } } else { @@ -5625,12 +5648,30 @@ } if (pmap_pte_w(pmap, ptep)) { - atomic_add_long(&pt_pv->pv_pmap->pm_stats.wired_count, - -1); - atomic_clear_long(ptep, pmap->pmap_bits[PG_W_IDX]); - - pa = *ptep & PG_FRAME; - m = PHYS_TO_VM_PAGE(pa); + /* + * DF-5244: same atomic clear-and-capture as the + * kernel branch above. Holding pt_pv does not + * exclude cmpset-based PTE teardown from the + * PMAP_PAGE_BACKING_SCAN paths, which take no pt_pv + * lock and no placemarker. + */ + pa = 0; + for (;;) { + pt_entry_t opte = *ptep; + cpu_ccfence(); + if ((opte & pmap->pmap_bits[PG_W_IDX]) == 0) + break; + if (atomic_cmpset_long(ptep, opte, + opte & ~pmap->pmap_bits[PG_W_IDX])) { + pa = opte & PG_FRAME; + break; + } + } + if (pa) { + atomic_add_long(&pt_pv->pv_pmap->pm_stats.wired_count, + -1); + m = PHYS_TO_VM_PAGE(pa); + } /* caller handles m->wire_count */ } pv_put(pt_pv); |