⬢ DragonFlyBSD Kernel Audit
DF-4147 / fix.diff
← back to finding ↓ download raw
--- a/sys/dev/misc/cuse/cuse.c
+++ b/sys/dev/misc/cuse/cuse.c
@@ -52,6 +52,9 @@
 
 #include <vm/vm.h>
 #include <vm/pmap.h>
+#include <vm/vm_map.h>
+#include <vm/vm_extern.h>
+#include <vm/vm_kern.h>
 #include <vm/vm_object.h>
 #include <vm/vm_page.h>
 #include <vm/vm_pager.h>
@@ -877,17 +880,86 @@
 	return (error);
 }
 
+/*
+ * Copy between the current process and another process's USER address
+ * space.  The peer address (uio_offset) belongs to the target process
+ * and must never be used as a raw kernel pointer: copyin()/copyout()
+ * only validate their user-side argument, so passing the peer address as
+ * the kernel side turns a server-chosen peer_ptr into an arbitrary
+ * kernel read/write primitive (DF-4147).
+ *
+ * Walk the target's vm map page-by-page through a temporary kernel
+ * mapping, exactly like procfs_rwmem() (sys/vfs/procfs/procfs_mem.c).
+ * Unmapped or kernel addresses fail with EFAULT.
+ */
 static int
 proc_rwmem_dfly(struct proc *p, struct uio *uio)
 {
-	struct iovec *iov = uio->uio_iov;
-	int error = 0;
+	struct vmspace *vm;
+	vm_map_t map;
+	vm_offset_t kva;
+	vm_offset_t uva;
+	vm_prot_t reqprot;
+	int error;
+	int writing;
+	int busy;
+	vm_page_t m;
 
-	if (uio->uio_rw == UIO_WRITE) {
-		error = copyin(iov->iov_base, (void *)uio->uio_offset, iov->iov_len);
-	} else {
-		error = copyout((void *)uio->uio_offset, iov->iov_base, iov->iov_len);
-	}
+	if (uio->uio_resid == 0)
+		return (0);
+
+	uva = (vm_offset_t)uio->uio_offset;
+
+	/* the peer address must be a user address of the target process */
+	if (uva >= VM_MAX_USER_ADDRESS ||
+	    uva + uio->uio_resid > VM_MAX_USER_ADDRESS ||
+	    uva + uio->uio_resid < uva)
+		return (EFAULT);
+
+	if (p->p_stat == SIDL || p->p_stat == SZOMB)
+		return (EFAULT);
+	if (p->p_flags & (P_WEXIT | P_INEXEC))
+		return (EFAULT);
+
+	vm = p->p_vmspace;
+	if (vmspace_getrefs(vm) < 0)
+		return (EFAULT);
+	vmspace_hold(vm);
+	map = &vm->vm_map;
+
+	writing = (uio->uio_rw == UIO_WRITE);
+	reqprot = VM_PROT_READ;
+	if (writing)
+		reqprot |= VM_PROT_WRITE | VM_PROT_OVERRIDE_WRITE;
+
+	kva = kmem_alloc_pageable(kernel_map, PAGE_SIZE, VM_SUBSYS_PROC);
+	error = 0;
+
+	do {
+		vm_offset_t page_off = uva - trunc_page(uva);
+		size_t len = szmin(PAGE_SIZE - page_off, uio->uio_resid);
+
+		m = vm_fault_page(map, trunc_page(uva), reqprot,
+		    VM_FAULT_NORMAL, &error, &busy);
+		if (error) {
+			KKASSERT(m == NULL);
+			error = EFAULT;
+			break;
+		}
+		pmap_kenter_quick(kva, VM_PAGE_TO_PHYS(m));
+		error = uiomove((caddr_t)(kva + page_off), len, uio);
+		pmap_kremove_quick(kva);
+
+		if (busy)
+			vm_page_wakeup(m);
+		else
+			vm_page_unhold(m);
+
+		uva = (vm_offset_t)uio->uio_offset;
+	} while (error == 0 && uio->uio_resid > 0);
+
+	vmspace_drop(vm);
+	kmem_free(kernel_map, kva, PAGE_SIZE);
 
 	return error;
 }