⬢ DragonFlyBSD Kernel Audit
DF-3235 / fix.diff
← back to finding ↓ download raw
--- a/sys/vfs/procfs/procfs_status.c
+++ b/sys/vfs/procfs/procfs_status.c
@@ -39,7 +39,10 @@
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/uio.h>
+#include <sys/refcount.h>
 #include <sys/malloc.h>
+
+MALLOC_DECLARE(M_PARGS);
 #include <sys/proc.h>
 #include <sys/caps.h>
 #include <sys/jail.h>
@@ -48,6 +51,8 @@
 #include <sys/resourcevar.h>
 #include <vfs/procfs/procfs.h>
 
+MALLOC_DECLARE(M_PARGS);
+
 #include <vm/vm.h>
 #include <vm/pmap.h>
 #include <vm/vm_param.h>
@@ -186,11 +191,20 @@
 	int i;
 	size_t bytes_left, done;
 	size_t buflen;
+	struct pargs *pa;
+	char *snap;
 
 	if (uio->uio_rw != UIO_READ)
 		return (EOPNOTSUPP);
 
 	/*
+	 * Snapshot buffer.  Allocated before any target pointers are
+	 * captured so a blocking allocation cannot invalidate anything.
+	 */
+	snap = kmalloc(UPMAP_MAXPROCTITLE, M_TEMP, M_WAITOK);
+	pa = NULL;
+
+	/*
 	 * If we are using the ps/cmdline caching, use that.  Otherwise
 	 * revert back to the old way which only implements full cmdline
 	 * for the current process and just p->p_comm for all other
@@ -215,6 +229,15 @@
 			if (bp[buflen] == 0)
 				break;
 		}
+		/*
+		 * The scan above is non-blocking, but uiomove() below can
+		 * block; blocking drops the p_token held by our caller
+		 * (pfs_pfind) and lwp_userunmap() could then free the lpmap.
+		 * Copy the title while we are guaranteed non-blocking.
+		 */
+		bcopy(bp, snap, buflen);
+		snap[buflen] = 0;
+		bp = snap;
 		buf = NULL;
 	} else if (p->p_upmap != NULL && p->p_upmap->proc_title[0] &&
 		   (ps_argsopen || (CHECKIO(curp, p) &&
@@ -232,14 +255,31 @@
 			if (bp[buflen] == 0)
 				break;
 		}
+		/* same as above: proc_userunmap() vs blocking uiomove() */
+		bcopy(bp, snap, buflen);
+		snap[buflen] = 0;
+		bp = snap;
 		buf = NULL;
 	} else if (p->p_args &&
 		   (ps_argsopen || (CHECKIO(curp, p) &&
 				    (p->p_flags & P_INEXEC) == 0 &&
 				    !p_trespass(curp->p_ucred, p->p_ucred))
 		   )) {
-		bp = p->p_args->ar_args;
-		buflen = p->p_args->ar_length;
+		/*
+		 * execve() in the target can free p_args while uiomove()
+		 * blocks (p_token is dropped on block).  Hold a reference,
+		 * exactly like sysctl_kern_proc_args() does, and copy the
+		 * (small) arguments into our snapshot buffer.
+		 */
+		pa = p->p_args;
+		refcount_acquire(&pa->ar_ref);
+		buflen = pa->ar_length;
+		if (buflen <= UPMAP_MAXPROCTITLE) {
+			bcopy(pa->ar_args, snap, buflen);
+			bp = snap;
+		} else {
+			bp = pa->ar_args;	/* reference keeps it alive */
+		}
 		buf = NULL;
 	} else if (p != curp) {
 		bp = p->p_comm;
@@ -290,7 +330,13 @@
 	}
 
 	error = uiomove_frombuf(bp, buflen, uio);
+	if (pa) {
+		if (refcount_release(&pa->ar_ref))
+			kfree(pa, M_PARGS);
+		pa = NULL;
+	}
 	if (buf)
 		kfree(buf, M_TEMP);
+	kfree(snap, M_TEMP);
 	return (error);
 }