โฌข DragonFlyBSD Kernel Audit
DF-1040 / cis_oob_harness.c
โ† back to finding โ†“ download raw
/*
 * DF-1040 โ€” userspace logic harness (object-level proof for a latent bug).
 *
 * The vulnerable function pccard_scan_cis() in sys/bus/pccard/pccard_cis.c
 * cannot be invoked on this audit guest because the KVM/QEMU machine has no
 * PCMCIA/CardBus bridge hardware (pccard is compiled into GENERIC but there
 * is no cbb/pcic bridge device to attach a card to, so pccard_attach_card()
 * is never called and pccard_scan_cis() is unreachable at runtime).
 *
 * This harness replicates the EXACT offset-computation logic of the
 * chain-transition loop at pccard_cis.c:386-437 to demonstrate, at the
 * object/logic level, that a card-supplied longlink_addr of 0xFFFFFFFF
 * produces a bus offset of 0x1FFFFFFFE for the dereference at line 413 โ€”
 * wildly outside the 4096-byte attribute-memory mapping allocated at
 * lines 130-131.  On real hardware this offset is fed to bus_space_read_1()
 * and page-faults the kernel.
 *
 * The harness also demonstrates that the proposed fix.diff rejects the
 * out-of-bounds target before the dereference.
 *
 * Build:  cc -O2 -o cis_oob_harness cis_oob_harness.c
 * Run:    ./cis_oob_harness
 */

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>

/* Mirror of sys/bus/pccard/pccard_cis.c:62 */
#define PCCARD_CIS_SIZE 4096

/* Mirror of pccard_cis_read_1() in sys/bus/pccard/pccardvar.h:255-256:
 *   bus_space_read_1((tuple)->memt, (tuple)->memh, (tuple)->mult*(idx0))
 * We model the byte offset that would be passed to bus_space_read_1. */
static uint64_t cis_byte_offset(unsigned long mult, unsigned long ptr) {
    return (uint64_t)mult * (uint64_t)ptr;
}

/* Replicate the chain-transition pointer setup (pccard_cis.c:393-394, 405-406)
 * and the unguarded dereference (pccard_cis.c:413).  Returns the byte offset
 * that bus_space_read_1 would be asked to read. */
static uint64_t chain_transition_deref(unsigned long mult, unsigned long ptr) {
    /* pccard_cis.c:393/405 */ /* tuple.mult = ...; */
    /* pccard_cis.c:394/406 */ /* tuple.ptr  = ...; */
    /* pccard_cis.c:413      */ /* tuple.code = pccard_cis_read_1(&tuple, tuple.ptr); */
    return cis_byte_offset(mult, ptr); /* == mult * ptr */
}

/* Replicate the proposed fix: reject targets that don't leave room for the
 * 5-byte LINKTARGET header (code, length, 'C','I','S') within the window.
 * Two-check form to be overflow-safe on both 32 and 64-bit. */
static int fix_rejects(unsigned long mult, unsigned long ptr) {
    if (ptr >= (unsigned long)PCCARD_CIS_SIZE)
        return 1;
    if (ptr + 4 >= (unsigned long)PCCARD_CIS_SIZE / mult)
        return 1;
    return 0;
}

struct case_t {
    const char *name;
    unsigned long mult;
    unsigned long ptr;       /* card-supplied longlink_addr or mfc[].addr */
    int expect_oob;          /* 1 = unguarded path reads out of bounds */
};

int main(void) {
    /* Cases mirror the finding's threat model.  The first is the PoC image:
     * CISTPL_LONGLINK_A with addr=0xFFFFFFFF, mult=2 (attribute memory). */
    struct case_t cases[] = {
        { "PoC longlink_A 0xFFFFFFFF mult=2 (attr mem)", 2, 0xFFFFFFFFUL, 1 },
        { "longlink_C 0xFFFFFFFF mult=1 (common mem)",   1, 0xFFFFFFFFUL, 1 },
        { "mfc entry 0xDEADBEEF mult=2",                 2, 0xDEADBEEFUL, 1 },
        { "mfc entry 0x80000000 mult=1",                 1, 0x80000000UL, 1 },
        { "boundary ptr=2044 mult=2 (needs 5 bytes)",    2, 2044,          1 },
        { "valid   ptr=100  mult=2",                     2, 100,           0 },
        { "valid   ptr=2043 mult=2 (last in-window)",    2, 2043,          0 },
        { "valid   ptr=4091 mult=1 (last in-window)",    1, 4091,          0 },
        { "valid   ptr=0    mult=2",                     2, 0,             0 },
    };
    int n = sizeof(cases)/sizeof(cases[0]);
    int fails = 0;

    printf("DF-1040 CIS longlink/MFC target validation harness\n");
    printf("PCCARD_CIS_SIZE = %u bytes (mapped window at pccard_cis.c:130-131)\n",
           PCCARD_CIS_SIZE);
    printf("pccard_cis_read_1 byte offset = mult * ptr  (pccardvar.h:255-256)\n");
    printf("chain-transition deref at pccard_cis.c:413 has NO bounds check\n");
    printf("%-48s %7s %12s %8s %8s %8s\n",
           "case", "mult", "byte_off", "expect", "is_oob", "fix_rej");
    printf("%-48s %7s %12s %8s %8s %8s\n",
           "----", "----", "--------", "------", "------", "-------");

    for (int i = 0; i < n; i++) {
        uint64_t off = chain_transition_deref(cases[i].mult, cases[i].ptr);
        int is_oob = (off + cases[i].mult * 4 >= PCCARD_CIS_SIZE);
        int rej = fix_rejects(cases[i].mult, cases[i].ptr);
        printf("%-48s %7lu %12llu %8s %8s %8s",
               cases[i].name, cases[i].mult,
               (unsigned long long)off,
               cases[i].expect_oob ? "OOB" : "in",
               is_oob ? "OOB" : "in",
               rej ? "yes" : "no");
        if (cases[i].expect_oob) {
            if (!is_oob) { printf("  <<< HARNESS BUG: expected OOB"); fails++; }
            if (!rej)    { printf("  <<< FIX BUG: should reject");    fails++; }
        } else {
            if (is_oob)  { printf("  <<< HARNESS BUG: unexpected OOB"); fails++; }
            if (rej)     { printf("  <<< FIX BUG: false reject");      fails++; }
        }
        printf("\n");
    }

    printf("\n");
    printf("Proof of primitive (PoC case):\n");
    {
        unsigned long mult = 2, ptr = 0xFFFFFFFFUL;
        uint64_t off = chain_transition_deref(mult, ptr);
        printf("  longlink_addr (card-supplied, 32-bit) = 0x%lX\n", ptr);
        printf("  tuple.mult (pccard_cis.c:393, attr mem) = %lu\n", mult);
        printf("  tuple.ptr  (pccard_cis.c:394)           = 0x%lX\n", ptr);
        printf("  bus_space_read_1 byte offset (line 413) = 0x%llX\n",
               (unsigned long long)off);
        printf("  mapped window size                      = %u\n", PCCARD_CIS_SIZE);
        printf("  offset exceeds window by                = %llu bytes\n",
               (unsigned long long)(off - PCCARD_CIS_SIZE));
        printf("  => on real PCMCIA hardware this is a wild bus_space_read_1\n");
        printf("     past the mapped resource => kernel page-fault panic.\n");
        printf("  fix.diff rejects this target: %s\n",
               fix_rejects(mult, ptr) ? "YES (continue, no deref)" : "NO (BUG)");
    }

    printf("\n%s\n", fails ? "FAIL: harness/fix logic error" :
           "PASS: all cases agree โ€” bug confirmed at logic level, fix is correct");
    return fails ? 1 : 0;
}