⬢ DragonFlyBSD Kernel Audit
DF-5190 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/dev/drm/linux_vmalloc.c b/sys/dev/drm/linux_vmalloc.c
--- a/sys/dev/drm/linux_vmalloc.c
+++ b/sys/dev/drm/linux_vmalloc.c
@@ -30,6 +30,8 @@
 #include <sys/queue.h>
+#include <sys/kernel.h>
 #include <vm/vm_extern.h>
+#include <vm/vm_kern.h>
 
 #include <linux/vmalloc.h>
 #include <linux/slab.h>
@@ -40,9 +42,17 @@ struct vmap {
 	void *addr;
 	int npages;
 	SLIST_ENTRY(vmap) vm_vmaps;
 };
 
+struct vmalloc_entry {
+	void *addr;
+	size_t size;
+	SLIST_ENTRY(vmalloc_entry) entries;
+};
+
 struct lock vmap_lock = LOCK_INITIALIZER("dlvml", 0, LK_CANRECURSE);
 
 SLIST_HEAD(vmap_list_head, vmap) vmap_list = SLIST_HEAD_INITIALIZER(vmap_list);
+static SLIST_HEAD(vmalloc_head, vmalloc_entry) vmalloc_list =
+    SLIST_HEAD_INITIALIZER(vmalloc_list);
@@ -105,21 +115,66 @@ is_vmalloc_addr(const void *x)
 	return false;
 }
 
+/*
+ * vmalloc-family allocations: page-aligned, whole-page, (optionally) zeroed
+ * kernel memory.  Backed by kmem_alloc3() instead of kmalloc() so the Linux
+ * contract holds: callers may map this memory to userspace page-by-page
+ * without exposing neighbouring kernel heap objects (the whole point of
+ * vmalloc_user()).
+ */
+static void *
+vmalloc_common(unsigned long size, int zero)
+{
+	struct vmalloc_entry *ve;
+	vm_offset_t addr;
+
+	size = (size + PAGE_SIZE - 1) & ~(unsigned long)PAGE_MASK;
+	if (size == 0)
+		size = PAGE_SIZE;
+
+	addr = kmem_alloc3(kernel_map, size, VM_SUBSYS_DRM, 0);
+	if (addr == 0)
+		return (NULL);
+
+	ve = kmalloc(sizeof(*ve), M_DRM, M_WAITOK | M_ZERO);
+	ve->addr = (void *)addr;
+	ve->size = size;
+	lockmgr(&vmap_lock, LK_EXCLUSIVE);
+	SLIST_INSERT_HEAD(&vmalloc_list, ve, entries);
+	lockmgr(&vmap_lock, LK_RELEASE);
+
+	if (zero)
+		bzero((void *)addr, size);
+
+	return ((void *)addr);
+}
+
 void *
 vmalloc(unsigned long size)
 {
-	return kmalloc(size, M_DRM, M_WAITOK);
+	return vmalloc_common(size, 0);
 }
 
 void *
 vzalloc(unsigned long size)
 {
-	return kmalloc(size, M_DRM, M_WAITOK | M_ZERO);
+	return vmalloc_common(size, 1);
 }
 
 /* allocate zeroed virtually contiguous memory for userspace */
 void *
 vmalloc_user(unsigned long size)
 {
-	return kmalloc(size, M_DRM, M_WAITOK | M_ZERO);
+	return vmalloc_common(size, 1);
 }
 
 void
 vfree(const void *addr)
 {
 	void *nc_addr;
+	struct vmalloc_entry *ve;
 
 	memcpy(&nc_addr, &addr, sizeof(void *));
+
+	lockmgr(&vmap_lock, LK_EXCLUSIVE);
+	SLIST_FOREACH(ve, &vmalloc_list, entries) {
+		if (ve->addr == nc_addr) {
+			SLIST_REMOVE(&vmalloc_list, ve, vmalloc_entry, entries);
+			break;
+		}
+	}
+	lockmgr(&vmap_lock, LK_RELEASE);
+	if (ve != NULL) {
+		kmem_free(kernel_map, (vm_offset_t)nc_addr, ve->size);
+		kfree(ve, M_DRM);
+		return;
+	}
+
+	/* Not a vmalloc-family pointer: legacy kmalloc fallback. */
 	kfree(nc_addr);
 }