DragonFlyBSD Kernel Audit
DF-2206 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/dev/drm/linux_iomapping.c b/sys/dev/drm/linux_iomapping.c
index 0000000..1111111 100644
--- a/sys/dev/drm/linux_iomapping.c
+++ b/sys/dev/drm/linux_iomapping.c
@@ -65,6 +65,21 @@
 	int indx;
 	vm_paddr_t paddr_end;
 
+	/*
+	 * DF-2206: The whole lookup + teardown + removal + free must run
+	 * under iomap_lock.  The list is process-global (shared across every
+	 * DRM device), so two concurrent iounmap() callers can otherwise
+	 * race: one frees an entry the other is still walking (UAF read /
+	 * wild pointer), or both pass the unlocked found-check and double-call
+	 * pmap_unmapdev()/SLIST_REMOVE on the same VA (double free / NULL
+	 * deref when SLIST_REMOVE walks past end of list).
+	 *
+	 * iomap_lock is initialised LK_CANRECURSE, and none of the called
+	 * helpers (pmap_unmapdev, pmap_change_attr, kfree) acquire it, so
+	 * holding it across the teardown is safe.
+	 */
+	lockmgr(&iomap_lock, LK_EXCLUSIVE);
+
 	SLIST_FOREACH_MUTABLE(imp, &iomap_list, im_iomaps, tmp_imp) {
 		if (imp->pmap_addr == ptr) {
 			found = 1;
@@ -73,6 +88,7 @@
 	}
 
 	if (!found) {
+		lockmgr(&iomap_lock, LK_RELEASE);
 		kprintf("iounmap: invalid address %p\n", ptr);
 		return;
 	}
@@ -95,7 +111,6 @@
 
 	pmap_unmapdev((vm_offset_t)imp->pmap_addr, imp->npages * PAGE_SIZE);
 
-	lockmgr(&iomap_lock, LK_EXCLUSIVE);
 	SLIST_REMOVE(&iomap_list, imp, iomap, im_iomaps);
 	lockmgr(&iomap_lock, LK_RELEASE);