DF-3191 / 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 112 113 114 115 116 117 118 119 120 | --- a/sys/vfs/fuse/fuse_vfsops.c 2026-09-08 06:30:24.086056927 +0000 +++ b/sys/vfs/fuse/fuse_vfsops.c 2026-09-08 06:30:24.110056618 +0000 @@ -165,6 +165,19 @@ vrele(devvp); return EBADF; } + + /* + * DF-3192: args.fd must be the /dev/fuse descriptor that was just + * looked up (devvp). devfs_get_cdevpriv() returns whatever private + * data ANY driver attached to the fd; passing e.g. a /dev/cuse fd + * made the kernel type-confuse that driver's private object with + * struct fuse_mount and scribble a fuse_mount layout into it. + */ + if (file->f_type != DTYPE_VNODE || file->f_data != devvp) { + dropfp(curthread, args.fd, file); + vrele(devvp); + return EINVAL; + } error = devfs_get_cdevpriv(file, (void**)&fmp); dropfp(curthread, args.fd, file); if (error) { @@ -173,7 +186,18 @@ } KKASSERT(fmp); - fmp->mp = mp; + /* + * DF-3191: one /dev/fuse descriptor serves exactly one mount. + * Without this guard a second mount(2) on the same fd re-initializes + * the live fmp (locks, IPC lists, node tree, rfnp) and the first + * unmount tears down the second mount's state (use-after-free / + * panic). Atomically claim the association. + */ + if (atomic_cmpset_ptr(&fmp->mp, (uintptr_t)NULL, (uintptr_t)mp) == 0) { + vrele(devvp); + return EBUSY; + } + fmp->dead = false; mtx_init(&fmp->mnt_lock, "fuse_mnt_lock"); mtx_init(&fmp->ipc_lock, "fuse_ipc_lock"); @@ -209,6 +233,7 @@ error = fuse_ipc_tx(fip); if (error) { + fmp->mp = NULL; /* release claim: fd may be retried */ vrele(devvp); return error; } @@ -220,6 +245,7 @@ if (fuse_cmp_version(fmp, 7, 0) < 0) { fuse_ipc_put(fip); + fmp->mp = NULL; /* release claim: fd may be retried */ vrele(devvp); return EPROTONOSUPPORT; } @@ -272,6 +298,20 @@ fuse_mount_kill(fmp); } + /* + * DF-3193: FUSE is MPSAFE, so VFS_STATFS/VFS_STATVFS are not + * serialized against unmount. Wait for in-flight IPCs to leave + * the queues before tearing down fmp; otherwise their wait/timeout + * paths wake up on freed memory. Owners remove themselves on + * timeout (bounded by fuse_ipc_wait retries), so this terminates. + */ + mtx_lock(&fmp->ipc_lock); + while (!TAILQ_EMPTY(&fmp->reply_head) || + !TAILQ_EMPTY(&fmp->request_head)) { + mtxsleep(&fmp->reply_head, &fmp->ipc_lock, 0, "fdrain", hz); + } + mtx_unlock(&fmp->ipc_lock); + /* Wait for helper thread to exit */ while (fmp->helper_td) { wakeup(&fmp->helper_td); --- a/sys/vfs/fuse/fuse_ipc.c 2026-09-08 06:30:24.086056927 +0000 +++ b/sys/vfs/fuse/fuse_ipc.c 2026-09-08 06:30:24.126056414 +0000 @@ -162,6 +162,7 @@ if (fuse_test_dead(fip->fmp)) { KKASSERT(!fuse_ipc_test_replied(fip)); + fuse_ipc_remove(fip); /* unqueue before caller put()s us */ fuse_ipc_set_replied(fip); return ENOTCONN; } @@ -263,6 +264,17 @@ wakeup(fmp); KNOTE(&fmp->kq.ki_note, 0); + /* + * DF-3193: fuse_mount_kill() may have set dead between our entry + * check and the enqueue. Re-check and unqueue ourselves so the + * unmount drain cannot wait on a request nobody will answer. + */ + if (__predict_false(fuse_test_dead(fmp))) { + fuse_ipc_remove(fip); + fuse_ipc_put(fip); + return ENOTCONN; + } + error = fuse_ipc_wait(fip); KKASSERT(fuse_ipc_test_replied(fip)); if (error) { @@ -304,6 +316,12 @@ wakeup(fmp); KNOTE(&fmp->kq.ki_note, 0); + if (__predict_false(fuse_test_dead(fmp))) { + fuse_ipc_remove(fip); + fuse_ipc_put(fip); + return ENOTCONN; + } + error = fuse_ipc_wait_sent(fip); if (error) fuse_ipc_put(fip); |