git: 6772a8ece2c0 - releng/14.3 - execve_block(): a mechanism for mutual exclusion with execve() on the process

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Tue, 30 Jun 2026 17:20:28 UTC
The branch releng/14.3 has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=6772a8ece2c01362e37d25be00967f815b9c2b62

commit 6772a8ece2c01362e37d25be00967f815b9c2b62
Author:     Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2026-05-26 17:36:20 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-06-30 02:32:01 +0000

    execve_block(): a mechanism for mutual exclusion with execve() on the process
    
    (cherry picked from commit e1a84b7708c2514769625c2af6c5034694013b6a)
    (cherry picked from commit dc94d15a7136c4cfe3342c005d8a319f0583ed9a)
    
    proc: Allow to make proc_rwmem() operate on a consistent address space
    
    (cherry picked from commit e1b0d051bbf7e4e730470cbd6622f71a639834c3)
    (cherry picked from commit e7578e37d832ecf1eee8cd3fed10df0e0cb1aa7e)
    
    kern_proc.c: ensure stability of the vmspace we read the strings from
    
    (cherry picked from commit a22c044cce41864a32af6b8281a3c416a2664d7f)
    (cherry picked from commit 1f062247ef4e920e7fdd1ebe91b4600d69fe1e91)
    
    kern_proc.c: ensure stability of the vmspace for sysctl kern.proc.vmmap
    
    (cherry picked from commit 5fe6e08d2db56b530f0df2903ef24cfabab7b8ea)
    (cherry picked from commit e856c10a4d6dfe30a730246b2050d9dd438085db)
    
    kern_proc.c: ensure stability of the vmspace for sysctl kern.proc.vm_layout
    
    (cherry picked from commit 31fa677e4af00b795bf671847b4dc72960c90f14)
    (cherry picked from commit da1398dd6962b458fdcf25799fcb512f4d9c070d)
    
    kern_proc.c: make kern.proc.osrel atomic
    
    (cherry picked from commit 80626f34ee985671bb8c60ee986b89587b7a1511)
    (cherry picked from commit b3f99ce653d456c9c829359b0dc83c4867f044b7)
    
    kern_proc.c: disallow execve around sysctl kern.proc.kstacks
    
    (cherry picked from commit 8b5abd9027b8b1f6290c756730ee3adebed007f4)
    (cherry picked from commit 197f367cdda7919578d15b1562225d6db462c5ad)
    
    kern_proc.c: disallow execve around sysctl kern.proc.rlimit
    
    (cherry picked from commit 5a91fa5a7656c99e527fe7e6f6bf6bd9e85ab589)
    (cherry picked from commit 35b95beb06cab0b10a7e3a697931851315fe3dc2)
    
    kern_event: block the target process from execing for sysctl kern.proc.kqueue
    
    (cherry picked from commit 79b384bd8fdff4869ce4799edb0342ff5c25b6fa)
    (cherry picked from commit adb14791b752cf0f07dce44d840f01340aa6d6cb)
    
    kern_resource.c: disallow execve around sysctl kern.proc.rlimitusage
    
    (cherry picked from commit 836749817036b90b60af0584fa21f2d9dbd60ff7)
    (cherry picked from commit 190d6677b11a1deed6bd0ef14fd4f990eea20f5b)
    
    kern_procctl(PROC_WX_MAPPINGS_PERMIT): ensure stability of the target vmspace
    
    (cherry picked from commit 6a572920f251ac8ac6a80a55d4d039736ea7dd65)
    (cherry picked from commit 23ebf0c97ac5fcd6adb6cda8a8707b99257721a6)
    
    linux_prlimit(): block execve for the target
    
    (cherry picked from commit e41c28e67fac9cd22a85b160c5c9d0477ed03600)
    (cherry picked from commit a11c4f036c1136ba90fbfda5d2025d18116562d8)
    
    pfs_readdir(): block the target process from execing
    
    (cherry picked from commit 4c0a0909f928baac982b7de4bfa5b5ca7de5291a)
    (cherry picked from commit caa2e5cf7cfb07c115d143023ffb21546a60556a)
    
    pseudofs: ensure that the target process vmspace is stable for VOP_READ/WRITE
    
    (cherry picked from commit a4993bac41350e85bc9affb862d2974a1a09bb5e)
    (cherry picked from commit ec0d7d01d6c45eff4043cf178addf65d1933d62a)
    
    procfs, linprocfs: ensure target process vmspace stability
    
    (cherry picked from commit 5db31f1b47b586a5091eb41fc957236f28b78dba)
    (cherry picked from commit d7d5337291c983ba969e24c95df0bcab46b44621)
    
    sys_set*id(9): wait for the execblocks to pass
    
    (cherry picked from commit d0384f50dad25d3358d76f7556969ea1d515ba68)
    (cherry picked from commit 34b95f53e7ac3a055b3d866d101a79e85d0022c4)
    
    kern_exec.c: explicitly include sys/limits.h for UINT_MAX
    
    (cherry picked from commit f5fce0bcc370a9700249e64901eabf2e0be4ecb0)
    (cherry picked from commit bb1154f3ea20291b0c02821743084a6f1a7dd844)
    
    Approved by:    so
    Security:       FreeBSD-SA-26:39.execve
    Security:       CVE-2026-49415
---
 sys/compat/linprocfs/linprocfs.c |  16 +---
 sys/compat/linux/linux_misc.c    |  15 +++
 sys/fs/cuse/cuse.c               |   4 +-
 sys/fs/procfs/procfs_map.c       |  17 ++--
 sys/fs/procfs/procfs_mem.c       |   6 +-
 sys/fs/pseudofs/pseudofs_vnops.c |  69 ++++++++++----
 sys/kern/kern_event.c            |  22 ++++-
 sys/kern/kern_exec.c             |  81 ++++++++++++++++-
 sys/kern/kern_exit.c             |   1 +
 sys/kern/kern_fork.c             |   1 +
 sys/kern/kern_proc.c             | 191 +++++++++++++++++++++++----------------
 sys/kern/kern_procctl.c          |  23 +++--
 sys/kern/kern_prot.c             |  20 +++-
 sys/kern/kern_resource.c         |  27 ++++--
 sys/kern/sys_process.c           | 124 +++++++++++++++++++++----
 sys/sys/imgact.h                 |   4 +
 sys/sys/proc.h                   |   3 +
 sys/sys/ptrace.h                 |  15 ++-
 18 files changed, 467 insertions(+), 172 deletions(-)

diff --git a/sys/compat/linprocfs/linprocfs.c b/sys/compat/linprocfs/linprocfs.c
index d4f38b491471..b42cbea61d09 100644
--- a/sys/compat/linprocfs/linprocfs.c
+++ b/sys/compat/linprocfs/linprocfs.c
@@ -1317,19 +1317,13 @@ linprocfs_doprocmaps(PFS_FILL_ARGS)
 	struct vattr vat;
 	bool private;
 
-	PROC_LOCK(p);
-	error = p_candebug(td, p);
-	PROC_UNLOCK(p);
-	if (error)
-		return (error);
-
 	if (uio->uio_rw != UIO_READ)
 		return (EOPNOTSUPP);
 
-	error = 0;
-	vm = vmspace_acquire_ref(p);
-	if (vm == NULL)
-		return (ESRCH);
+	error = proc_vmspace_ref(td, p, PRVM_BLOCK_EXEC | PRVM_CHECK_DEBUG,
+	    &vm);
+	if (error != 0)
+		return (error);
 
 	if (SV_CURPROC_FLAG(SV_LP64))
 		l_map_str = l64_map_str;
@@ -1427,7 +1421,7 @@ linprocfs_doprocmaps(PFS_FILL_ARGS)
 		}
 	}
 	vm_map_unlock_read(map);
-	vmspace_free(vm);
+	proc_vmspace_unref(td, p, PRVM_CHECK_DEBUG | PRVM_BLOCK_EXEC, vm);
 
 	return (error);
 }
diff --git a/sys/compat/linux/linux_misc.c b/sys/compat/linux/linux_misc.c
index b87c05322e16..41977e5a899c 100644
--- a/sys/compat/linux/linux_misc.c
+++ b/sys/compat/linux/linux_misc.c
@@ -2007,6 +2007,7 @@ linux_prlimit64(struct thread *td, struct linux_prlimit64_args *args)
 	u_int which;
 	int flags;
 	int error;
+	bool exec_blocked;
 
 	if (args->new == NULL && args->old != NULL) {
 		if (linux_get_dummy_limit(args->resource, &rlim)) {
@@ -2034,6 +2035,7 @@ linux_prlimit64(struct thread *td, struct linux_prlimit64_args *args)
 			return (error);
 	}
 
+	exec_blocked = false;
 	flags = PGET_HOLD | PGET_NOTWEXIT;
 	if (args->new != NULL)
 		flags |= PGET_CANDEBUG;
@@ -2046,6 +2048,14 @@ linux_prlimit64(struct thread *td, struct linux_prlimit64_args *args)
 		error = pget(args->pid, flags, &p);
 		if (error != 0)
 			return (error);
+		exec_blocked = true;
+		PROC_LOCK(p);
+		execve_block_wait(td, p);
+		error = args->new != NULL ? p_candebug(td, p) :
+		    p_cansee(td, p);
+		PROC_UNLOCK(p);
+		if (error != 0)
+			goto out;
 	}
 	if (args->old != NULL) {
 		PROC_LOCK(p);
@@ -2068,6 +2078,11 @@ linux_prlimit64(struct thread *td, struct linux_prlimit64_args *args)
 		error = kern_proc_setrlimit(td, p, which, &nrlim);
 
  out:
+	if (exec_blocked) {
+		PROC_LOCK(p);
+		execve_unblock(td, p);
+		PROC_UNLOCK(p);
+	}
 	PRELE(p);
 	return (error);
 }
diff --git a/sys/fs/cuse/cuse.c b/sys/fs/cuse/cuse.c
index d63a7d4691cf..b9423fd3543d 100644
--- a/sys/fs/cuse/cuse.c
+++ b/sys/fs/cuse/cuse.c
@@ -914,7 +914,7 @@ cuse_proc2proc_copy(struct proc *proc_s, vm_offset_t data_s,
 		};
 
 		PHOLD(proc_s);
-		error = proc_rwmem(proc_s, &uio);
+		error = proc_rwmem(proc_s, &uio, 0);
 		PRELE(proc_s);
 
 	} else if (proc_cur == proc_s) {
@@ -933,7 +933,7 @@ cuse_proc2proc_copy(struct proc *proc_s, vm_offset_t data_s,
 		};
 
 		PHOLD(proc_d);
-		error = proc_rwmem(proc_d, &uio);
+		error = proc_rwmem(proc_d, &uio, 0);
 		PRELE(proc_d);
 	} else {
 		error = EINVAL;
diff --git a/sys/fs/procfs/procfs_map.c b/sys/fs/procfs/procfs_map.c
index db6b225f6426..5ccef979856b 100644
--- a/sys/fs/procfs/procfs_map.c
+++ b/sys/fs/procfs/procfs_map.c
@@ -42,6 +42,7 @@
 #include <sys/malloc.h>
 #include <sys/mount.h>
 #include <sys/proc.h>
+#include <sys/ptrace.h>
 #include <sys/resourcevar.h>
 #include <sys/rwlock.h>
 #include <sys/sbuf.h>
@@ -95,15 +96,14 @@ procfs_doprocmap(PFS_FILL_ARGS)
 	bool wrap32;
 #endif
 
-	PROC_LOCK(p);
-	error = p_candebug(td, p);
-	PROC_UNLOCK(p);
-	if (error)
-		return (error);
-
 	if (uio->uio_rw != UIO_READ)
 		return (EOPNOTSUPP);
 
+	error = proc_vmspace_ref(td, p, PRVM_BLOCK_EXEC | PRVM_CHECK_DEBUG,
+	    &vm);
+	if (error != 0)
+		return (error);
+
 #ifdef COMPAT_FREEBSD32
 	wrap32 = false;
 	if (SV_CURPROC_FLAG(SV_ILP32)) {
@@ -113,9 +113,6 @@ procfs_doprocmap(PFS_FILL_ARGS)
 	}
 #endif
 
-	vm = vmspace_acquire_ref(p);
-	if (vm == NULL)
-		return (ESRCH);
 	map = &vm->vm_map;
 	vm_map_lock_read(map);
 	VM_MAP_ENTRY_FOREACH(entry, map) {
@@ -240,6 +237,6 @@ procfs_doprocmap(PFS_FILL_ARGS)
 		}
 	}
 	vm_map_unlock_read(map);
-	vmspace_free(vm);
+	proc_vmspace_unref(td, p, PRVM_BLOCK_EXEC | PRVM_CHECK_DEBUG, vm);
 	return (error);
 }
diff --git a/sys/fs/procfs/procfs_mem.c b/sys/fs/procfs/procfs_mem.c
index c74954db37d6..5fc0c1acf140 100644
--- a/sys/fs/procfs/procfs_mem.c
+++ b/sys/fs/procfs/procfs_mem.c
@@ -61,11 +61,7 @@ procfs_doprocmem(PFS_FILL_ARGS)
 	if (uio->uio_resid == 0)
 		return (0);
 
-	PROC_LOCK(p);
-	error = p_candebug(td, p);
-	PROC_UNLOCK(p);
-	if (error == 0)
-		error = proc_rwmem(p, uio);
+	error = proc_rwmem(p, uio, PRVM_CHECK_DEBUG | PRVM_BLOCK_EXEC);
 
 	return (error);
 }
diff --git a/sys/fs/pseudofs/pseudofs_vnops.c b/sys/fs/pseudofs/pseudofs_vnops.c
index 0bdfedffafcb..0695c2f727ea 100644
--- a/sys/fs/pseudofs/pseudofs_vnops.c
+++ b/sys/fs/pseudofs/pseudofs_vnops.c
@@ -37,6 +37,7 @@
 #include <sys/ctype.h>
 #include <sys/dirent.h>
 #include <sys/fcntl.h>
+#include <sys/imgact.h>
 #include <sys/limits.h>
 #include <sys/lock.h>
 #include <sys/malloc.h>
@@ -132,6 +133,7 @@ static int
 pfs_lookup_proc(pid_t pid, struct proc **p)
 {
 	struct proc *proc;
+	struct thread *td;
 
 	proc = pfind(pid);
 	if (proc == NULL)
@@ -141,8 +143,10 @@ pfs_lookup_proc(pid_t pid, struct proc **p)
 		return (0);
 	}
 	_PHOLD(proc);
-	PROC_UNLOCK(proc);
+	td = curthread;
+	execve_block_wait(td, proc);
 	*p = proc;
+	PROC_UNLOCK(proc);
 	return (1);
 }
 
@@ -672,6 +676,7 @@ pfs_read(struct vop_read_args *va)
 	struct pfs_node *pn = pvd->pvd_pn;
 	struct uio *uio = va->a_uio;
 	struct proc *proc;
+	struct thread *td;
 	struct sbuf *sb = NULL;
 	int error, locked;
 	off_t buflen, buflim;
@@ -690,21 +695,30 @@ pfs_read(struct vop_read_args *va)
 	if (pn->pn_fill == NULL)
 		PFS_RETURN (EIO);
 
+	td = curthread;
+
 	/*
 	 * This is necessary because either process' privileges may
 	 * have changed since the open() call.
 	 */
-	if (!pfs_visible(curthread, pn, pvd->pvd_pid, &proc))
+	if (!pfs_visible(td, pn, pvd->pvd_pid, &proc))
 		PFS_RETURN (EIO);
-	if (proc != NULL) {
-		_PHOLD(proc);
-		PROC_UNLOCK(proc);
-	}
 
 	vhold(vn);
 	locked = VOP_ISLOCKED(vn);
 	VOP_UNLOCK(vn);
 
+	if (proc != NULL) {
+		_PHOLD(proc);
+		execve_block_wait(td, proc);
+		if (!pfs_visible_proc(td, pn, proc)) {
+			PROC_UNLOCK(proc);
+			error = EIO;
+			goto ret;
+		}
+		PROC_UNLOCK(proc);
+	}
+
 	if (pn->pn_flags & PFS_RAWRD) {
 		PFS_TRACE(("%zd resid", uio->uio_resid));
 		error = pn_fill(curthread, proc, pn, NULL, uio);
@@ -774,8 +788,12 @@ pfs_read(struct vop_read_args *va)
 ret:
 	vn_lock(vn, locked | LK_RETRY);
 	vdrop(vn);
-	if (proc != NULL)
-		PRELE(proc);
+	if (proc != NULL) {
+		PROC_LOCK(proc);
+		execve_unblock(td, proc);
+		_PRELE(proc);
+		PROC_UNLOCK(proc);
+	}
 	PFS_RETURN (error);
 }
 
@@ -846,6 +864,7 @@ pfs_readdir(struct vop_readdir_args *va)
 	struct pfs_node *pd = pvd->pvd_pn;
 	pid_t pid = pvd->pvd_pid;
 	struct proc *p, *proc;
+	struct thread *td;
 	struct pfs_node *pn;
 	struct uio *uio;
 	struct pfsentry *pfsent, *pfsent2;
@@ -884,11 +903,13 @@ pfs_readdir(struct vop_readdir_args *va)
 	KASSERT(pid == NO_PID || proc != NULL,
 	    ("%s(): no process for pid %lu", __func__, (unsigned long)pid));
 
+	td = curthread;
 	if (pid != NO_PID) {
 		PROC_LOCK(proc);
 
 		/* check if the directory is visible to the caller */
 		if (!pfs_visible_proc(curthread, pd, proc)) {
+			execve_unblock(td, proc);
 			_PRELE(proc);
 			PROC_UNLOCK(proc);
 			pfs_unlock(pd);
@@ -956,6 +977,7 @@ pfs_readdir(struct vop_readdir_args *va)
 		resid -= PFS_DELEN;
 	}
 	if (proc != NULL) {
+		execve_unblock(td, proc);
 		_PRELE(proc);
 		PROC_UNLOCK(proc);
 	}
@@ -1080,6 +1102,7 @@ pfs_write(struct vop_write_args *va)
 	struct pfs_node *pn = pvd->pvd_pn;
 	struct uio *uio = va->a_uio;
 	struct proc *proc;
+	struct thread *td;
 	struct sbuf sb;
 	int error;
 
@@ -1099,36 +1122,44 @@ pfs_write(struct vop_write_args *va)
 	if (uio->uio_resid > PFS_MAXBUFSIZ)
 		PFS_RETURN (EIO);
 
+	td = curthread;
+
 	/*
 	 * This is necessary because either process' privileges may
 	 * have changed since the open() call.
 	 */
-	if (!pfs_visible(curthread, pn, pvd->pvd_pid, &proc))
+	if (!pfs_visible(td, pn, pvd->pvd_pid, &proc))
 		PFS_RETURN (EIO);
 	if (proc != NULL) {
 		_PHOLD(proc);
+		execve_block_wait(td, proc);
+		if (!pfs_visible_proc(td, pn, proc)) {
+			PROC_UNLOCK(proc);
+			error = EIO;
+			goto out;
+		}
 		PROC_UNLOCK(proc);
 	}
 
 	if (pn->pn_flags & PFS_RAWWR) {
 		error = pn_fill(curthread, proc, pn, NULL, uio);
-		if (proc != NULL)
-			PRELE(proc);
-		PFS_RETURN (error);
+		goto out;
 	}
 
 	sbuf_uionew(&sb, uio, &error);
-	if (error) {
-		if (proc != NULL)
-			PRELE(proc);
-		PFS_RETURN (error);
-	}
+	if (error != 0)
+		goto out;
 
 	error = pn_fill(curthread, proc, pn, &sb, uio);
 
 	sbuf_delete(&sb);
-	if (proc != NULL)
-		PRELE(proc);
+out:
+	if (proc != NULL) {
+		PROC_LOCK(proc);
+		execve_unblock(td, proc);
+		_PRELE(proc);
+		PROC_UNLOCK(proc);
+	}
 	PFS_RETURN (error);
 }
 
diff --git a/sys/kern/kern_event.c b/sys/kern/kern_event.c
index eb77a5064113..089548dad97d 100644
--- a/sys/kern/kern_event.c
+++ b/sys/kern/kern_event.c
@@ -50,6 +50,7 @@
 #include <sys/filedesc.h>
 #include <sys/filio.h>
 #include <sys/fcntl.h>
+#include <sys/imgact.h>
 #include <sys/kthread.h>
 #include <sys/selinfo.h>
 #include <sys/queue.h>
@@ -3028,10 +3029,6 @@ sysctl_kern_proc_kqueue(SYSCTL_HANDLER_ARGS)
 	if ((u_int)arg2 > 2 || (u_int)arg2 == 0)
 		return (EINVAL);
 
-	error = pget((pid_t)name[0], PGET_HOLD | PGET_CANDEBUG, &p);
-	if (error != 0)
-		return (error);
-
 	td = curthread;
 #ifdef FREEBSD_COMPAT32
 	compat32 = SV_CURPROC_FLAG(SV_ILP32);
@@ -3039,6 +3036,17 @@ sysctl_kern_proc_kqueue(SYSCTL_HANDLER_ARGS)
 	compat32 = false;
 #endif
 
+	error = pget((pid_t)name[0], PGET_NOTWEXIT, &p);
+	if (error != 0)
+		return (error);
+
+	_PHOLD(p);
+	execve_block_wait(td, p);
+	error = p_candebug(td, p);
+	if (error != 0)
+		goto out1;
+	PROC_UNLOCK(p);
+
 	s = sbuf_new_for_sysctl(&sm, NULL, 0, req);
 	if (s == NULL) {
 		error = ENOMEM;
@@ -3059,7 +3067,11 @@ sysctl_kern_proc_kqueue(SYSCTL_HANDLER_ARGS)
 	sbuf_delete(s);
 
 out:
-	PRELE(p);
+	PROC_LOCK(p);
+out1:
+	execve_unblock(td, p);
+	_PRELE(p);
+	PROC_UNLOCK(p);
 	return (error);
 }
 
diff --git a/sys/kern/kern_exec.c b/sys/kern/kern_exec.c
index 484adaac91ec..b6b599ffd281 100644
--- a/sys/kern/kern_exec.c
+++ b/sys/kern/kern_exec.c
@@ -26,7 +26,6 @@
  * SUCH DAMAGE.
  */
 
-#include <sys/cdefs.h>
 #include "opt_capsicum.h"
 #include "opt_hwpmc_hooks.h"
 #include "opt_ktrace.h"
@@ -45,6 +44,7 @@
 #include <sys/imgact.h>
 #include <sys/imgact_elf.h>
 #include <sys/kernel.h>
+#include <sys/limits.h>
 #include <sys/lock.h>
 #include <sys/malloc.h>
 #include <sys/mman.h>
@@ -373,6 +373,77 @@ execve_nosetid(struct image_params *imgp)
 	}
 }
 
+/*
+ * Returns true if the execblock was obtained, in this case the
+ * process lock is kept.  Returns false if the execblock was not
+ * obtained, but the function slept and the lock was dropped.
+ */
+bool
+execve_block(struct thread *td, struct proc *p)
+{
+	PROC_LOCK_ASSERT(p, MA_OWNED);
+	MPASS(td == curthread);
+	MPASS(p != td->td_proc || (p->p_flag & P_INEXEC) == 0);
+
+	if (p != td->td_proc && (p->p_flag & P_INEXEC) != 0) {
+		p->p_flag2 |= P2_INEXEC_WAIT;
+		msleep(&p->p_execblock, &p->p_mtx, PDROP, "inexec", 0);
+		return (false);
+	}
+	MPASS(p->p_execblock < UINT_MAX);
+	p->p_execblock++;
+	return (true);
+}
+
+/*
+ * Might drop the process lock internally, callers must re-check the
+ * invariants afterward.
+ */
+void
+execve_block_wait(struct thread *td, struct proc *p)
+{
+	bool first;
+
+	PROC_ASSERT_HELD(p);
+	PROC_LOCK_ASSERT(p, MA_OWNED);
+
+	for (first = true;; first = false) {
+		if (!first)
+			PROC_LOCK(p);
+		if (execve_block(td, p))
+			return;
+	}
+}
+
+void
+execve_unblock(struct thread *td, struct proc *p)
+{
+	PROC_LOCK_ASSERT(p, MA_OWNED);
+	MPASS(td == curthread);
+
+	MPASS(p->p_execblock > 0);
+	p->p_execblock--;
+	if (p->p_execblock == 0 && (p->p_flag2 & P2_INEXEC_WAIT) != 0) {
+		p->p_flag2 &= ~P2_INEXEC_WAIT;
+		wakeup(&p->p_execblock);
+	}
+}
+
+void
+execve_block_pass(struct thread *td)
+{
+	struct proc *p;
+
+	MPASS(td == curthread);
+	p = td->td_proc;
+	PROC_LOCK_ASSERT(p, MA_OWNED);
+	
+	while (p->p_execblock != 0) {
+		p->p_flag2 |= P2_INEXEC_WAIT;
+		msleep(&p->p_execblock, &p->p_mtx, 0, "exeblk", 0);
+	}
+}
+
 /*
  * In-kernel implementation of execve().  All arguments are assumed to be
  * userspace pointers from the passed thread.
@@ -428,6 +499,7 @@ do_execve(struct thread *td, struct image_args *args, struct mac *mac_p,
 	PROC_LOCK(p);
 	KASSERT((p->p_flag & P_INEXEC) == 0,
 	    ("%s(): process already has P_INEXEC flag", __func__));
+	execve_block_pass(td);
 	p->p_flag |= P_INEXEC;
 	PROC_UNLOCK(p);
 
@@ -896,7 +968,11 @@ interpret:
 	 * as we're now a bona fide freshly-execed process.
 	 */
 	KNOTE_LOCKED(p->p_klist, NOTE_EXEC);
+	MPASS(p->p_execblock == 0);
+	if ((p->p_flag2 & P2_INEXEC_WAIT) != 0)
+		wakeup(&p->p_execblock);
 	p->p_flag &= ~P_INEXEC;
+	p->p_flag2 &= ~P2_INEXEC_WAIT;
 
 	/* clear "fork but no exec" flag, as we _are_ execing */
 	p->p_acflag &= ~AFORK;
@@ -978,7 +1054,10 @@ exec_fail_dealloc:
 exec_fail:
 		/* we're done here, clear P_INEXEC */
 		PROC_LOCK(p);
+		if ((p->p_flag2 & P2_INEXEC_WAIT) != 0)
+			wakeup(&p->p_execblock);
 		p->p_flag &= ~P_INEXEC;
+		p->p_flag2 &= ~P2_INEXEC_WAIT;
 		PROC_UNLOCK(p);
 
 		SDT_PROBE1(proc, , , exec__failure, error);
diff --git a/sys/kern/kern_exit.c b/sys/kern/kern_exit.c
index 0c3070f2e360..5bcd4219bf91 100644
--- a/sys/kern/kern_exit.c
+++ b/sys/kern/kern_exit.c
@@ -325,6 +325,7 @@ exit1(struct thread *td, int rval, int signo)
 	while (p->p_lock > 0)
 		msleep(&p->p_lock, &p->p_mtx, PWAIT, "exithold", 0);
 
+	MPASS(p->p_execblock == 0);
 	PROC_UNLOCK(p);
 	/* Drain the limit callout while we don't have the proc locked */
 	callout_drain(&p->p_limco);
diff --git a/sys/kern/kern_fork.c b/sys/kern/kern_fork.c
index ee58ad42bce6..96ed239fafb0 100644
--- a/sys/kern/kern_fork.c
+++ b/sys/kern/kern_fork.c
@@ -384,6 +384,7 @@ do_fork(struct thread *td, struct fork_req *fr, struct proc *p2, struct thread *
 
 	bzero(&p2->p_startzero,
 	    __rangeof(struct proc, p_startzero, p_endzero));
+	p2->p_execblock = 0;
 
 	/* Tell the prison that we exist. */
 	prison_proc_hold(p2->p_ucred->cr_prison);
diff --git a/sys/kern/kern_proc.c b/sys/kern/kern_proc.c
index b7fa7bfb60e9..d8a57ac46673 100644
--- a/sys/kern/kern_proc.c
+++ b/sys/kern/kern_proc.c
@@ -45,6 +45,7 @@
 #include <sys/eventhandler.h>
 #include <sys/exec.h>
 #include <sys/fcntl.h>
+#include <sys/imgact.h>
 #include <sys/ipc.h>
 #include <sys/jail.h>
 #include <sys/kernel.h>
@@ -1833,8 +1834,8 @@ pargs_drop(struct pargs *pa)
 }
 
 static int
-proc_read_string(struct thread *td, struct proc *p, const char *sptr, char *buf,
-    size_t len)
+proc_read_string(struct thread *td, struct vmspace *vm, const char *sptr,
+    char *buf, size_t len)
 {
 	ssize_t n;
 
@@ -1843,7 +1844,7 @@ proc_read_string(struct thread *td, struct proc *p, const char *sptr, char *buf,
 	 * and is aligned at the end of the page, and the following page is not
 	 * mapped.
 	 */
-	n = proc_readmem(td, p, (vm_offset_t)sptr, buf, len);
+	n = vmspace_iop(td, vm, (vm_offset_t)sptr, buf, len, UIO_READ);
 	if (n <= 0)
 		return (ENOMEM);
 	return (0);
@@ -1859,8 +1860,8 @@ enum proc_vector_type {
 
 #ifdef COMPAT_FREEBSD32
 static int
-get_proc_vector32(struct thread *td, struct proc *p, char ***proc_vectorp,
-    size_t *vsizep, enum proc_vector_type type)
+get_proc_vector32(struct thread *td, struct proc *p, struct vmspace *vm,
+    char ***proc_vectorp, size_t *vsizep, enum proc_vector_type type)
 {
 	struct freebsd32_ps_strings pss;
 	Elf32_Auxinfo aux;
@@ -1871,8 +1872,8 @@ get_proc_vector32(struct thread *td, struct proc *p, char ***proc_vectorp,
 	int i, error;
 
 	error = 0;
-	if (proc_readmem(td, p, PROC_PS_STRINGS(p), &pss, sizeof(pss)) !=
-	    sizeof(pss))
+	if (vmspace_iop(td, vm, PROC_PS_STRINGS(p), &pss, sizeof(pss),
+	    UIO_READ) != sizeof(pss))
 		return (ENOMEM);
 	switch (type) {
 	case PROC_ARG:
@@ -1895,8 +1896,8 @@ get_proc_vector32(struct thread *td, struct proc *p, char ***proc_vectorp,
 		if (vptr % 4 != 0)
 			return (ENOEXEC);
 		for (ptr = vptr, i = 0; i < PROC_AUXV_MAX; i++) {
-			if (proc_readmem(td, p, ptr, &aux, sizeof(aux)) !=
-			    sizeof(aux))
+			if (vmspace_iop(td, vm, ptr, &aux, sizeof(aux),
+			    UIO_READ) != sizeof(aux))
 				return (ENOMEM);
 			if (aux.a_type == AT_NULL)
 				break;
@@ -1912,7 +1913,7 @@ get_proc_vector32(struct thread *td, struct proc *p, char ***proc_vectorp,
 		return (EINVAL);
 	}
 	proc_vector32 = malloc(size, M_TEMP, M_WAITOK);
-	if (proc_readmem(td, p, vptr, proc_vector32, size) != size) {
+	if (vmspace_iop(td, vm, vptr, proc_vector32, size, UIO_READ) != size) {
 		error = ENOMEM;
 		goto done;
 	}
@@ -1933,8 +1934,8 @@ done:
 #endif
 
 static int
-get_proc_vector(struct thread *td, struct proc *p, char ***proc_vectorp,
-    size_t *vsizep, enum proc_vector_type type)
+get_proc_vector(struct thread *td, struct proc *p, struct vmspace *vm,
+    char ***proc_vectorp, size_t *vsizep, enum proc_vector_type type)
 {
 	struct ps_strings pss;
 	Elf_Auxinfo aux;
@@ -1944,11 +1945,13 @@ get_proc_vector(struct thread *td, struct proc *p, char ***proc_vectorp,
 	int i;
 
 #ifdef COMPAT_FREEBSD32
-	if (SV_PROC_FLAG(p, SV_ILP32) != 0)
-		return (get_proc_vector32(td, p, proc_vectorp, vsizep, type));
+	if (SV_PROC_FLAG(p, SV_ILP32) != 0) {
+		return (get_proc_vector32(td, p, vm, proc_vectorp,
+		    vsizep, type));
+	}
 #endif
-	if (proc_readmem(td, p, PROC_PS_STRINGS(p), &pss, sizeof(pss)) !=
-	    sizeof(pss))
+	if (vmspace_iop(td, vm, PROC_PS_STRINGS(p), &pss, sizeof(pss),
+	    UIO_READ) != sizeof(pss))
 		return (ENOMEM);
 	switch (type) {
 	case PROC_ARG:
@@ -1986,8 +1989,8 @@ get_proc_vector(struct thread *td, struct proc *p, char ***proc_vectorp,
 		 * to the allocated proc_vector.
 		 */
 		for (ptr = vptr, i = 0; i < PROC_AUXV_MAX; i++) {
-			if (proc_readmem(td, p, ptr, &aux, sizeof(aux)) !=
-			    sizeof(aux))
+			if (vmspace_iop(td, vm, ptr, &aux, sizeof(aux),
+			    UIO_READ) != sizeof(aux))
 				return (ENOMEM);
 			if (aux.a_type == AT_NULL)
 				break;
@@ -2009,7 +2012,7 @@ get_proc_vector(struct thread *td, struct proc *p, char ***proc_vectorp,
 		return (EINVAL); /* In case we are built without INVARIANTS. */
 	}
 	proc_vector = malloc(size, M_TEMP, M_WAITOK);
-	if (proc_readmem(td, p, vptr, proc_vector, size) != size) {
+	if (vmspace_iop(td, vm, vptr, proc_vector, size, UIO_READ) != size) {
 		free(proc_vector, M_TEMP);
 		return (ENOMEM);
 	}
@@ -2025,6 +2028,7 @@ static int
 get_ps_strings(struct thread *td, struct proc *p, struct sbuf *sb,
     enum proc_vector_type type)
 {
+	struct vmspace *vm;
 	size_t done, len, nchr, vsize;
 	int error, i;
 	char **proc_vector, *sptr;
@@ -2037,9 +2041,14 @@ get_ps_strings(struct thread *td, struct proc *p, struct sbuf *sb,
 	 */
 	nchr = 2 * (PATH_MAX + ARG_MAX);
 
-	error = get_proc_vector(td, p, &proc_vector, &vsize, type);
+	error = proc_vmspace_ref(td, p, PRVM_BLOCK_EXEC |
+	    PRVM_CHECK_VISIBILITY, &vm);
 	if (error != 0)
 		return (error);
+
+	error = get_proc_vector(td, p, vm, &proc_vector, &vsize, type);
+	if (error != 0)
+		goto out;
 	for (done = 0, i = 0; i < (int)vsize && done < nchr; i++) {
 		/*
 		 * The program may have scribbled into its argv array, e.g. to
@@ -2049,7 +2058,7 @@ get_ps_strings(struct thread *td, struct proc *p, struct sbuf *sb,
 		if (proc_vector[i] == NULL)
 			break;
 		for (sptr = proc_vector[i]; ; sptr += GET_PS_STRINGS_CHUNK_SZ) {
-			error = proc_read_string(td, p, sptr, pss_string,
+			error = proc_read_string(td, vm, sptr, pss_string,
 			    sizeof(pss_string));
 			if (error != 0)
 				goto done;
@@ -2066,6 +2075,8 @@ get_ps_strings(struct thread *td, struct proc *p, struct sbuf *sb,
 	}
 done:
 	free(proc_vector, M_TEMP);
+out:
+	proc_vmspace_unref(td, p, PRVM_BLOCK_EXEC | PRVM_CHECK_VISIBILITY, vm);
 	return (error);
 }
 
@@ -2086,11 +2097,17 @@ proc_getenvv(struct thread *td, struct proc *p, struct sbuf *sb)
 int
 proc_getauxv(struct thread *td, struct proc *p, struct sbuf *sb)
 {
+	struct vmspace *vm;
 	size_t vsize, size;
 	char **auxv;
 	int error;
 
-	error = get_proc_vector(td, p, &auxv, &vsize, PROC_AUX);
+	error = proc_vmspace_ref(td, p, PRVM_BLOCK_EXEC | PRVM_CHECK_DEBUG,
+	    &vm);
+	if (error != 0)
+		return (error);
+	error = get_proc_vector(td, p, vm, &auxv, &vsize, PROC_AUX);
+	proc_vmspace_unref(td, p, PRVM_BLOCK_EXEC | PRVM_CHECK_DEBUG, vm);
 	if (error == 0) {
 #ifdef COMPAT_FREEBSD32
 		if (SV_PROC_FLAG(p, SV_ILP32) != 0)
@@ -2403,6 +2420,7 @@ sysctl_kern_proc_ovmmap(SYSCTL_HANDLER_ARGS)
 	int error, *name;
 	struct vnode *vp;
 	struct proc *p;
+	struct thread *td;
 	vm_map_t map;
 	struct vmspace *vm;
 
@@ -2411,11 +2429,12 @@ sysctl_kern_proc_ovmmap(SYSCTL_HANDLER_ARGS)
 		return (EINVAL);
 
 	name = (int *)arg1;
+	td = curthread;
 	error = pget((pid_t)name[0], PGET_WANTREAD, &p);
 	if (error != 0)
 		return (error);
-	vm = vmspace_acquire_ref(p);
-	if (vm == NULL) {
+	error = proc_vmspace_ref(td, p, PRVM_CHECK_DEBUG, &vm);
+	if (error != 0) {
 		PRELE(p);
 		return (ESRCH);
 	}
@@ -2527,7 +2546,7 @@ sysctl_kern_proc_ovmmap(SYSCTL_HANDLER_ARGS)
 		}
 	}
 	vm_map_unlock_read(map);
-	vmspace_free(vm);
+	proc_vmspace_unref(td, p, PRVM_CHECK_DEBUG, vm);
 	PRELE(p);
 	free(kve, M_TEMP);
 	return (error);
@@ -2618,6 +2637,7 @@ kern_proc_vmmap_out(struct proc *p, struct sbuf *sb, ssize_t maxlen, int flags)
 	struct vmspace *vm;
 	struct cdev *cdev;
 	struct cdevsw *csw;
+	struct thread *td;
 	vm_offset_t addr;
 	unsigned int last_timestamp;
 	int error, ref;
@@ -2629,10 +2649,11 @@ kern_proc_vmmap_out(struct proc *p, struct sbuf *sb, ssize_t maxlen, int flags)
 
 	_PHOLD(p);
 	PROC_UNLOCK(p);
-	vm = vmspace_acquire_ref(p);
-	if (vm == NULL) {
+	td = curthread;
+	error = proc_vmspace_ref(td, p, PRVM_CHECK_DEBUG, &vm);
+	if (error != 0) {
 		PRELE(p);
-		return (ESRCH);
+		return (error);
 	}
 	kve = malloc(sizeof(*kve), M_TEMP, M_WAITOK | M_ZERO);
 
@@ -2747,7 +2768,7 @@ kern_proc_vmmap_out(struct proc *p, struct sbuf *sb, ssize_t maxlen, int flags)
 			if (vp != NULL) {
 				vn_fullpath(vp, &fullpath, &freepath);
 				kve->kve_vn_type = vntype_to_kinfo(vp->v_type);
-				cred = curthread->td_ucred;
+				cred = td->td_ucred;
 				vn_lock(vp, LK_SHARED | LK_RETRY);
 				if (VOP_GETATTR(vp, &va, cred) == 0) {
 					kve->kve_vn_fileid = va.va_fileid;
@@ -2803,7 +2824,7 @@ kern_proc_vmmap_out(struct proc *p, struct sbuf *sb, ssize_t maxlen, int flags)
 		}
 	}
 	vm_map_unlock_read(map);
-	vmspace_free(vm);
+	proc_vmspace_unref(td, p, PRVM_CHECK_DEBUG, vm);
 	PRELE(p);
 	free(kve, M_TEMP);
 	return (error);
@@ -2842,7 +2863,7 @@ sysctl_kern_proc_kstack(SYSCTL_HANDLER_ARGS)
 	struct kinfo_kstack *kkstp;
 	int error, i, *name, numthreads;
 	lwpid_t *lwpidarray;
-	struct thread *td;
+	struct thread *td, *ctd;
 	struct stack *st;
 	struct sbuf sb;
 	struct proc *p;
@@ -2853,7 +2874,8 @@ sysctl_kern_proc_kstack(SYSCTL_HANDLER_ARGS)
 		return (EINVAL);
 
 	name = (int *)arg1;
-	error = pget((pid_t)name[0], PGET_NOTINEXEC | PGET_WANTREAD, &p);
+	ctd = curthread;
+	error = pget((pid_t)name[0], PGET_WANTREAD, &p);
 	if (error != 0)
 		return (error);
 
@@ -2862,6 +2884,14 @@ sysctl_kern_proc_kstack(SYSCTL_HANDLER_ARGS)
 
 	lwpidarray = NULL;
 	PROC_LOCK(p);
+	execve_block_wait(ctd, p);
+	error = p_candebug(ctd, p);
+	if (error != 0) {
+		execve_unblock(ctd, p);
+		_PRELE(p);
+		PROC_UNLOCK(p);
+		return (error);
+	}
 	do {
 		if (lwpidarray != NULL) {
 			free(lwpidarray, M_TEMP);
@@ -2874,15 +2904,6 @@ sysctl_kern_proc_kstack(SYSCTL_HANDLER_ARGS)
 		PROC_LOCK(p);
 	} while (numthreads < p->p_numthreads);
 
-	/*
-	 * XXXRW: During the below loop, execve(2) and countless other sorts
-	 * of changes could have taken place.  Should we check to see if the
-	 * vmspace has been replaced, or the like, in order to prevent
-	 * giving a snapshot that spans, say, execve(2), with some threads
-	 * before and some after?  Among other things, the credentials could
*** 695 LINES SKIPPED ***