โฌข DragonFlyBSD Kernel Audit
DF-1020 / harness.c
โ† back to finding โ†“ download raw
/*
 * DF-1020 harness โ€” fw_bus_explore_callback unconditional OOB csrrom write
 *
 * BUG: fw_bus_explore_callback() at sys/bus/firewire/firewire.c:1500 writes
 *   fc->ongodev->csrrom[(fc->ongoaddr - CSRROMOFF)/4] = ntohl(rfp->mode.rresq.data);
 * UNCONDITIONALLY before the bounds check at :1558:
 *   if((fc->ongoaddr - CSRROMOFF) > CSRROMSIZE) goto nextnode;
 *
 * At :1520, fc->ongoaddr += csrreg->val * 4, where csrreg->val is a 24-bit
 * field set from device data (via the write at :1500). ongoaddr is a 16-bit
 * bitfield (firewirereg.h:107-109), so csrreg->val * 4 can wrap it. After
 * wrapping, the next response write at :1500 indexes csrrom[] out of bounds.
 *
 * csrrom is u_int32_t[256] (CSRROMSIZE/4 = 0x400/4 = 256) in struct fw_device
 * (firewirereg.h:54). Writes past index 255 corrupt:
 *   - fw_device.rcnt, fw_device.fc (kernel pointer!), fw_device.status,
 *     fw_device.link (STAILQ next pointer)
 *   - Adjacent heap objects
 *
 * A malicious FireWire device replies to bus exploration RREQQ reads at
 * offset 0x418+ with key=0x81 (CROM_UDIR) and a crafted val to wrap
 * ongoaddr, then sends data to be written at the wrapped (OOB) address.
 *
 * This harness demonstrates the wrapping arithmetic. Runtime trigger
 * requires FireWire hardware โ€” this QEMU guest has none.
 *
 * Build: cc -o harness harness.c
 * Run:   ./harness
 */
#include <stdio.h>
#include <stdint.h>

#define CSRROMOFF   0x400
#define CSRROMSIZE  0x400

int main(void)
{
    /* Simulate ongoaddr as a 16-bit bitfield (firewirereg.h:109) */
    uint32_t ongoaddr = CSRROMOFF;  /* starts at Config ROM base */
    uint32_t csrrom_entries = CSRROMSIZE / 4;  /* 256 u_int32_t */

    printf("DF-1020: fw_bus_explore_callback OOB csrrom write\n");
    printf("  CSRROMOFF:  0x%X\n", CSRROMOFF);
    printf("  CSRROMSIZE: 0x%X\n", CSRROMSIZE);
    printf("  csrrom[]:   %u entries (u_int32_t)\n", csrrom_entries);
    printf("  ongoaddr:   16-bit bitfield (firewirereg.h:109)\n\n");

    /* Simulate normal exploration: read 4 entries (16 bytes of Config ROM) */
    printf("  Normal exploration (first 4 entries):\n");
    for (int i = 0; i < 4; i++) {
        int idx = (ongoaddr - CSRROMOFF) / 4;
        printf("    csrrom[%d] at ongoaddr=0x%X โ€” OK\n", idx, ongoaddr);
        ongoaddr += 4;
    }

    /* Now simulate a malicious device sending csrreg with key=0x81, val crafted */
    printf("\n  Malicious device sends key=0x81 (CROM_UDIR), val=0x3FFF:\n");
    /* csrreg is { val:24, key:8 } on LE, so the u_int32_t = val | (key<<24) */
    uint32_t device_data = 0x3FFF;  /* val=0x3FFF, key=0x00 initially */
    /* On the wire after ntohl: key would be in high byte */
    /* But we read it from csrrom which was set from ntohl(rfp->mode.rresq.data) */
    uint32_t csrreg_val = 0x3FFF;  /* 24-bit val */
    printf("    csrreg->val = 0x%X (24-bit, device-controlled)\n", csrreg_val);

    /* ongoaddr += csrreg->val * 4 โ€” with 16-bit truncation */
    uint32_t new_ongoaddr = (ongoaddr + csrreg_val * 4) & 0xFFFF;
    printf("    ongoaddr += val*4 = 0x%X + 0x%X = 0x%X\n",
           ongoaddr, csrreg_val * 4, ongoaddr + csrreg_val * 4);
    printf("    After 16-bit truncation: ongoaddr = 0x%X\n", new_ongoaddr);
    ongoaddr = new_ongoaddr;

    /* Next write at line 1500 uses this wrapped ongoaddr */
    if (ongoaddr >= CSRROMOFF) {
        int idx = (ongoaddr - CSRROMOFF) / 4;
        if (idx >= (int)csrrom_entries) {
            printf("\n  *** OOB WRITE: csrrom[%d] (max valid index: %d) ***\n",
                   idx, csrrom_entries - 1);
            printf("  Overflows past csrrom[256] into rcnt, fc (kernel ptr!),\n");
            printf("  status, link โ€” adjacent fw_device / heap.\n");
        }
    } else {
        /* ongoaddr < CSRROMOFF: (ongoaddr - CSRROMOFF) is a huge u_int32_t */
        uint32_t raw_idx = (ongoaddr - CSRROMOFF) / 4;
        printf("\n  *** OOB WRITE: ongoaddr=0x%X < CSRROMOFF=0x%X ***\n",
               ongoaddr, CSRROMOFF);
        printf("  (ongoaddr - CSRROMOFF) = 0x%X (unsigned underflow)\n",
               ongoaddr - CSRROMOFF);
        printf("  index = 0x%X = %u โ€” massive OOB write into kernel heap\n",
               raw_idx, raw_idx);
    }

    printf("\n  Bounds check at :1558 is AFTER the write at :1500 โ€” too late.\n");
    printf("  No authentication needed: malicious FireWire device on the bus.\n");
    return 0;
}