DF-0047 / trigger_stub.c
/* * DF-0047 trigger stub. * * mtx_wait_link() (sys/kern/kern_mutex.c:948-1029) has a TOCTOU window that * permanently leaks a mutex, deadlocking the kernel. The ONLY kernel code path * that can open the window passes PCATCH into mtx_lock_ex()/mtx_lock_ex_link(): * * sys/vfs/nfs/nfs_socket.c:2112 error = mtx_lock_ex(mtx, slpflag, slptimeo); * sys/vfs/nfs/nfs_socket.c:2184 error = mtx_lock_ex_link(mtx, &rep->r_link, ...); * * where slpflag = PCATCH only when the mount has NFSMNT_INT (the `intr` mount * option). No other mtx_lock_* caller in sys/ passes PCATCH, so the bug is * reachable ONLY from an interruptible NFS client operation that is signalled * while blocked on the (contended) NFS socket mutex. * * The race: * 1. nfs recv loop blocks in mtx_wait_link on the socket mutex. * 2. tsleep() returns EINTR (signal delivered, PCATCH). * 3. kern_mutex.c:1002 switch(link->state) reads MTX_LINK_LINKED_EX. * 4. kern_mutex.c:1012 mtx_delete_link() tries to acquire LINKSPIN. * WINDOW: another CPU releases the mutex and runs mtx_chain_link_ex() * (kern_mutex.c:750), which sets link->state = MTX_LINK_ACQUIRED and * mtx->mtx_owner = curthread, then wakeups us. * 5. mtx_delete_link now sees MTX_LINK_ACQUIRED -> `default` no-op * (kern_mutex.c:935). The link is NOT removed (chain already did). * 6. Back in mtx_wait_link: the switch already dispatched on the OLD state; * it does NOT re-check. Falls through; line 1023 unconditionally sets * link->state = MTX_LINK_IDLE and returns the EINTR error. * 7. Caller (nfs) sees the error and does NOT call mtx_unlock, yet the * mutex IS held exclusively by this thread (mtx_owner set in step 4). * -> the mutex is permanently leaked -> every later acquisition hangs. * * Why this stub does not run on the audit guest: * The trigger needs a working NFS server + an `intr` mount + concurrent NFS * I/O to create socket-mutex contention + a signal landing in the ~10-cycle * window between kern_mutex.c:1002 and the LINKSPIN acquire inside * mtx_delete_link. The QEMU audit guest has no reachable NFS server * (loopback nfsd/mountd/mount_nfs does not come up cleanly on the minimal * image), so the path is not reachable here, and even when reachable the * window is a non-deterministic multi-instruction race. Reproduction would * require an NFS export, an `intr` mount, two threads doing concurrent NFS * I/O, and a third thread SIGKILL/stop-ing the I/O thread millions of times. * * The fix.diff in this folder closes the window by re-checking link->state * after mtx_delete_link and treating a (now-)ACQUIRED link as success. * * To actually exercise (on a host with a real NFS server): * mount_nfs -o intr server:/export /nfsmp * # run many concurrent readers while killing/stop-ing them repeatedly * for i in $(jot 50); do dd if=/nfsmp/big bs=4k of=/dev/null & done * pkill -STOP dd; sleep 0.001; pkill -CONT dd # repeat for hours * # symptom: a thread stuck forever in mtx_wait_link / nfs recv -> system * # NFS I/O wedged (D-state processes, no progress). */ int main(void) { return 0; } |