⬢ DragonFlyBSD Kernel Audit
DF-5244 / fix.diff
← back to finding ↓ download raw
--- 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);