NetBSD-Bugs archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
Re: kern/60568: panic locking against myself (p->p_lock) in NFS from sysctl_vmproc
Can you please try the attached patch?
(Warning: untested on my end. But if it makes your system panic,
can't be worse than what you're already observing!)
# HG changeset patch
# User Taylor R Campbell <riastradh%NetBSD.org@localhost>
# Date 1786327290 0
# Mon Aug 10 02:01:30 2026 +0000
# Branch trunk
# Node ID 627f345a67f1beb9c8d462266402bb4226768c88
# Parent b125bad3265e13dfd5a772b3676744fca85ce488
# EXP-Topic riastradh-pr60568-sysctlvmproclock
WIP: sysctl vm.proc.*: Fix locking protocol.
Take p_lock only long enough for proc_vmspace_getref, and then rely
only on a read lock on p_reflock.
New subroutine proc_find_reflocked is like proc_find_locked but also
returns with p_reflock held, taking locks in the correct order
(proc_lock -> p_reflock -> p_lock) to make this work safely. (It
could just as well return with only p_reflock held, not p_lock, but
it presumably needs p_lock for KAUTH_PROCESS_CANSEE, so why bother
unlocking and relocking in the caller immediately?)
PR kern/60568: panic locking against myself (p->p_lock) in NFS from
sysctl_vmproc
diff -r b125bad3265e -r 627f345a67f1 sys/kern/kern_proc.c
--- a/sys/kern/kern_proc.c Mon Aug 03 19:24:48 2026 +0000
+++ b/sys/kern/kern_proc.c Mon Aug 10 02:01:30 2026 +0000
@@ -1789,7 +1789,7 @@ int
proc_vmspace_getref(struct proc *p, struct vmspace **vm)
{
- /* XXXCDC: how should locking work here? */
+ KASSERT(p == curproc || mutex_owned(p->p_lock));
/* curproc exception is for coredump. */
@@ -2946,7 +2946,20 @@ fill_kproc2(struct proc *p, struct kinfo
}
}
-
+/*
+ * proc_find_reflocked(l, &p, pid, op)
+ *
+ * Look up a process p by pid, taking l->l_proc if pid == -1, and
+ * take the mutex p->p_lock if pid != -1. Verify whether the
+ * caller is allowed to see the target process before succeeding.
+ * Return nonzero error on failure.
+ *
+ * Invariant on successful return:
+ *
+ * pid == -1 || mutex_owned(p->p_lock)
+ *
+ * Caller is responsible for mutex_exit(p->p_lock) if pid != -1.
+ */
int
proc_find_locked(struct lwp *l, struct proc **p, pid_t pid)
{
@@ -2961,7 +2974,8 @@ proc_find_locked(struct lwp *l, struct p
if (*p == NULL) {
if (pid != -1)
mutex_exit(&proc_lock);
- return SET_ERROR(ESRCH);
+ error = SET_ERROR(ESRCH);
+ goto out;
}
if (pid != -1)
mutex_enter((*p)->p_lock);
@@ -2973,7 +2987,70 @@ proc_find_locked(struct lwp *l, struct p
if (error) {
if (pid != -1)
mutex_exit((*p)->p_lock);
+ goto out;
}
+out: KASSERT(error != 0 || pid != -1 || mutex_owned((*p)->p_lock));
+ return error;
+}
+
+/*
+ * proc_find_reflocked(l, &p, pid, op)
+ *
+ * Look up a process p by pid, taking l->l_proc if pid == -1, and
+ * take _both_ the mutex p->p_lock _and_ p->p_reflock as a reader
+ * or writer according to op if pid != -1. Verify whether the
+ * caller is allowed to see the target process before succeeding.
+ * Return nonzero error on failure.
+ *
+ * Invariants on successful return:
+ *
+ * pid == -1 || mutex_owned(p->p_lock)
+ * pid == -1 || rw_lock_held(p->p_lock)
+ * pid == -1 || op != RW_READER || rw_read_held(p->p_lock)
+ * pid == -1 || op != RW_WRITER || rw_write_held(p->p_lock)
+ *
+ * Caller is responsible for mutex_exit(p->p_lock) _and_
+ * rw_exit(&p->p_reflock) if pid != -1.
+ */
+int
+proc_find_reflocked(struct lwp *l, struct proc **p, pid_t pid, krw_t op)
+{
+ int error;
+
+ mutex_enter(&proc_lock);
+ if (pid == -1)
+ *p = l->l_proc;
+ else
+ *p = proc_find(pid);
+
+ if (*p == NULL) {
+ if (pid != -1)
+ mutex_exit(&proc_lock);
+ error = SET_ERROR(ESRCH);
+ goto out;
+ }
+ if (pid != -1) {
+ rw_enter(&(*p)->p_reflock, op);
+ mutex_enter((*p)->p_lock);
+ }
+ mutex_exit(&proc_lock);
+
+ error = kauth_authorize_process(l->l_cred,
+ KAUTH_PROCESS_CANSEE, *p,
+ KAUTH_ARG(KAUTH_REQ_PROCESS_CANSEE_ENTRY), NULL, NULL);
+ if (error) {
+ if (pid != -1) {
+ mutex_exit((*p)->p_lock);
+ rw_exit(&(*p)->p_reflock);
+ }
+ goto out;
+ }
+out: KASSERT(error != 0 || pid == -1 || mutex_owned((*p)->p_lock));
+ KASSERT(error != 0 || pid == -1 || rw_lock_held(&(*p)->p_reflock));
+ KASSERT(error != 0 || pid == -1 || op != RW_READER ||
+ rw_read_held(&(*p)->p_reflock));
+ KASSERT(error != 0 || pid == -1 || op != RW_WRITER ||
+ rw_write_held(&(*p)->p_reflock));
return error;
}
diff -r b125bad3265e -r 627f345a67f1 sys/sys/proc.h
--- a/sys/sys/proc.h Mon Aug 03 19:24:48 2026 +0000
+++ b/sys/sys/proc.h Mon Aug 10 02:01:30 2026 +0000
@@ -496,6 +496,8 @@ extern struct proc *initproc; /* Process
extern const struct proclist_desc proclists[];
int proc_find_locked(struct lwp *, struct proc **, pid_t);
+int proc_find_reflocked(struct lwp *, struct proc **, pid_t,
+ krw_t);
proc_t * proc_find_raw(pid_t);
proc_t * proc_find(pid_t); /* Find process by ID */
proc_t * proc_find_lwpid(pid_t); /* Find process by LWP ID */
diff -r b125bad3265e -r 627f345a67f1 sys/uvm/uvm_map.c
--- a/sys/uvm/uvm_map.c Mon Aug 03 19:24:48 2026 +0000
+++ b/sys/uvm/uvm_map.c Mon Aug 10 02:01:30 2026 +0000
@@ -5377,6 +5377,7 @@ fill_vmentries(struct lwp *l, pid_t pid,
struct vm_map_entry *entry;
char *dp;
size_t count, vmesize;
+ bool locked = false;
if (elem_size == 0 || elem_size > 2 * sizeof(*vme))
return EINVAL;
@@ -5391,15 +5392,42 @@ fill_vmentries(struct lwp *l, pid_t pid,
} else
vmesize = 0;
- if ((error = proc_find_locked(l, &p, pid)) != 0)
+ /*
+ * Look up the pid and:
+ *
+ * 1. take p->p_lock so we can proc_vmspace_getref,
+ *
+ * 2. take a read lock on p->p_reflock so the process cannot
+ * exit while we're doing things that can't hold p->p_lock.
+ */
+ if ((error = proc_find_reflocked(l, &p, pid, RW_READER)) != 0)
return error;
+ KASSERT(pid == -1 || mutex_owned(p->p_lock));
+ KASSERT(pid == -1 || rw_read_held(&p->p_reflock));
+ locked = true;
vme = NULL;
count = 0;
+ /*
+ * Get a reference to the vmspace while we still hold
+ * p->p_lock. Then release p->p_lock so we can safely allocate
+ * memory, do vnode_to_path, &c., without deadlocking against
+ * the page daemon or proc lookup shenanigans (e.g., nfs intr
+ * signal catching) in vnode_to_path.
+ */
if ((error = proc_vmspace_getref(p, &vm)) != 0)
goto out;
-
+ if (pid != -1) {
+ mutex_exit(p->p_lock);
+ locked = false;
+ }
+
+ /*
+ * Take a read lock on the VM map to iterate over it.
+ *
+ * XXX Should we kmem_alloc before locking the VM map?
+ */
map = &vm->vm_map;
vm_map_lock_read(map);
@@ -5416,12 +5444,16 @@ fill_vmentries(struct lwp *l, pid_t pid,
}
count++;
}
+
vm_map_unlock_read(map);
uvmspace_free(vm);
out:
- if (pid != -1)
- mutex_exit(p->p_lock);
+ if (pid != -1) {
+ if (locked)
+ mutex_exit(p->p_lock);
+ rw_exit(&p->p_reflock);
+ }
if (error == 0) {
const u_int esize = uimin(sizeof(*vme), elem_size);
dp = oldp;
Home |
Main Index |
Thread Index |
Old Index