DF-3017 / fix.diff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | --- a/sys/sys/devfs.h 2026-09-05 10:07:15.510812707 +0000 +++ b/sys/sys/devfs.h 2026-09-05 10:08:18.410014538 +0000 @@ -324,6 +324,14 @@ #define DEVFS_NLINKSWAIT 0x400 /* NLinks final */ /* + * devfs_allocv() is active on the node and may be using it across a + * devfs_lock drop (getnewvnode()/vget()). devfs_freep() must defer + * the physical free until allocv() revalidates the node. + */ +#define DEVFS_ALLOCVINPROG 0x800 /* allocv() active on node */ +#define DEVFS_FREEWAIT 0x1000 /* freep deferred to allocv */ + +/* * Clone helper stuff */ #define DEVFS_BITMAP_INITIAL_SIZE 1 --- a/sys/vfs/devfs/devfs_core.c 2026-09-05 10:07:15.510812707 +0000 +++ b/sys/vfs/devfs/devfs_core.c 2026-09-05 10:08:18.422014386 +0000 @@ -308,6 +308,13 @@ KKASSERT(node); /* + * Prevent devfs_freep() from physically freeing the node while we + * have to release devfs_lock below. The node is only revalidated + * under devfs_lock after each drop. + */ + node->flags |= DEVFS_ALLOCVINPROG; + + /* * devfs master lock must not be held across a vget() call, we have * to hold our ad-hoc vp to avoid a free race from destroying the * contents of the structure. The vget() will interlock recycles @@ -321,6 +328,12 @@ vdrop(vp); lockmgr(&devfs_lock, LK_EXCLUSIVE); if (error == 0) { + if (node->flags & DEVFS_DESTROYED) { + vput(vp); + *vpp = NULL; + error = ENOENT; + goto out; + } *vpp = vp; goto out; } @@ -342,6 +355,19 @@ vp = *vpp; + /* + * The node could have been unlinked and destroyed by a concurrent + * devfs_freep() while devfs_lock was dropped (freep defers the + * physical free while DEVFS_ALLOCVINPROG is set). Revalidate. + */ + if (node->flags & DEVFS_DESTROYED) { + vp->v_type = VBAD; + vx_put(vp); + *vpp = NULL; + error = ENOENT; + goto out; + } + if (node->v_node != NULL) { vp->v_type = VBAD; vx_put(vp); @@ -386,6 +412,17 @@ vx_downgrade(vp); /* downgrade VX lock to VN lock */ out: + /* + * We are the last potential user of the node. Clear the + * in-progress flag and, if a devfs_freep() ran while we were + * unlocked (deferring its physical destruction), complete it. + */ + KKASSERT(lockstatus(&devfs_lock, curthread) == LK_EXCLUSIVE); + if (node->flags & DEVFS_ALLOCVINPROG) { + node->flags &= ~DEVFS_ALLOCVINPROG; + if (node->flags & DEVFS_DESTROYED) + devfs_freep(node); /* completes deferred free */ + } return error; } @@ -440,7 +477,11 @@ * issues. */ if (node->flags & DEVFS_DESTROYED) { - if ((node->flags & DEVFS_NLINKSWAIT) && + if (node->flags & DEVFS_FREEWAIT) { + /* completion from devfs_allocv(): physically free */ + kfree(node->d_dir.d_name, M_DEVFS); + objcache_put(devfs_node_cache, node); + } else if ((node->flags & DEVFS_NLINKSWAIT) && node->nlinks == 0) { kprintf("devfs: final node '%s' on nlinks\n", node->d_dir.d_name); @@ -525,6 +566,13 @@ */ if (node->nlinks) { node->flags |= DEVFS_NLINKSWAIT; + } else if (node->flags & DEVFS_ALLOCVINPROG) { + /* + * A devfs_allocv() is active on this node with devfs_lock + * dropped. Defer the physical free; allocv() completes it + * once it revalidates the node. + */ + node->flags |= DEVFS_FREEWAIT; } else { if (node->d_dir.d_name) { kfree(node->d_dir.d_name, M_DEVFS); |