โฌข DragonFlyBSD Kernel Audit
DF-0983 / urtwn_efuse_harness.c
โ† back to finding โ†“ download raw
/*
 * DF-0983 โ€” userspace harness replicating the kernel's efuse-parsing logic
 * found in sys/bus/u4b/wlan/if_urtwn.c (urtwn_efuse_read + urtwn_efuse_read_data).
 *
 * The kernel reads efuse bytes from a (malicious) RTL8188EU USB dongle and uses
 * an attacker-controlled `off` byte to index into `sc->rom` (a 512-byte
 * union urtwn_rom embedded in struct urtwn_softc). There is NO bounds check
 * tying `off` to the 512-byte rom size, so a crafted extended-header pair
 * (first_reg=0xef, second_reg=0xf0) yields off=127 and writes land at
 * rom[127*8 + i*2 + {0,1}] = rom[1016..1023] โ€” 504..511 bytes past the
 * 512-byte buffer, corrupting the softc fields that follow (last_rom_addr,
 * callouts, sc_mtx driver lock).
 *
 * This harness reproduces that exact indexing on a faithful buffer model and
 * shows the out-of-bounds write. Run twice: once as-is (VULNERABLE) and once
 * compiled with -DFIXED to confirm the proposed bounds check eliminates it.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define URTWN_CHIP_88E    0x10
#define URTWN_EFUSE_SIZE  512          /* union urtwn_rom == 512 bytes */
#define SOFTC_TAIL        512          /* bytes after rom inside softc (model) */

/* Faithful copy of urtwn_efuse_read_data (if_urtwn.c:1755). */
static void efuse_read_data(uint8_t *rom, uint8_t off, uint8_t msk,
                            const uint8_t **p /* efuse stream cursor */)
{
    uint8_t reg;
    for (int i = 0; i < 4; i++) {
        if (msk & (1 << i))
            continue;
        reg = *(*p)++;                                  /* urtwn_efuse_read_next */
        rom[off * 8 + i * 2 + 0] = reg;
        reg = *(*p)++;
        rom[off * 8 + i * 2 + 1] = reg;
    }
}

int main(void)
{
    /* Model the softc region: [rom(512)] [tail(512)]. rom == sc->rom. */
    uint8_t softc[URTWN_EFUSE_SIZE + SOFTC_TAIL];
    uint8_t *rom = softc;
    memset(softc, 0x00, sizeof(softc));
    /* mark the tail as canary so we can see the overflow */
    memset(softc + URTWN_EFUSE_SIZE, 0xCC, SOFTC_TAIL);

    uint32_t chip = URTWN_CHIP_88E;

    /*
     * Malicious efuse stream. The kernel reads one byte, loops while != 0xff.
     *   byte 0 = 0xef  -> (reg & 0x1f)==0x0f  -> extended-header branch
     *                    off = reg>>5 = 7
     *   byte 1 = 0xf0  -> (reg & 0x0f)=0 != 0x0f
     *                    off = ((0xf0)>>1)|7 = 0x78|0x07 = 127
     *                    msk = 0xf0 & 0xf = 0   (all 4 words written)
     *   (efuse_read_data consumes 8 more bytes, one per rom slot)
     *   byte 2..9      -> payload written to rom[1016..1023]
     *   byte 10 = 0xff -> loop exits
     */
    uint8_t efuse[] = { 0xef, 0xf0,
                        0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,
                        0xff };
    const uint8_t *cur = efuse;
    const uint8_t *stream_end = efuse + sizeof(efuse);

    uint8_t reg = *cur++;                               /* first read, before loop */
    int overflows = 0;
    unsigned max_idx = 0;

    while (reg != 0xff) {
        uint8_t off, msk;
        if ((chip & URTWN_CHIP_88E) && (reg & 0x1f) == 0x0f) {
            off = reg >> 5;
            reg = *cur++;                               /* read NEXT reg */
            if ((reg & 0x0f) != 0x0f)
                off = ((reg & 0xf0) >> 1) | off;
            else { reg = *cur++; continue; }
        } else {
            off = reg >> 4;
        }
        msk = reg & 0xf;

#ifdef FIXED
        /* Proposed fix: bounds-check off against the rom size. */
        if ((unsigned)off * 8 + 7 >= URTWN_EFUSE_SIZE) {
            printf("FIX: off=%u would overflow rom[%u..%u] (size %d) -> skipped\n",
                   off, off*8, off*8+7, URTWN_EFUSE_SIZE);
            reg = *cur++;
            continue;
        }
#endif
        /* track the indices BEFORE writing (kernel writes unconditionally) */
        for (int i = 0; i < 4; i++) {
            if (msk & (1 << i)) continue;
            unsigned idx0 = off*8 + i*2 + 0, idx1 = off*8 + i*2 + 1;
            if (idx0 >= URTWN_EFUSE_SIZE) { overflows++; if (idx0>max_idx) max_idx=idx0; }
            if (idx1 >= URTWN_EFUSE_SIZE) { overflows++; if (idx1>max_idx) max_idx=idx1; }
        }
        efuse_read_data(rom, off, msk, &cur);
        reg = *cur++;
        if (cur > stream_end) break;                    /* safety for harness */
    }

    printf("chip=0x%x  rom_size=%d\n", chip, URTWN_EFUSE_SIZE);
    printf("crafted off=127 -> writes rom[1016..1023] (0x%03X..0x%03X)\n", 1016, 1023);
    printf("OOB writes past rom end: %d  (max index %u)\n", overflows, max_idx);

    /* inspect the canary tail (bytes 504..511 past the rom are rom[1016..1023]) */
    int hit = 0;
    for (int i = URTWN_EFUSE_SIZE; i < (int)sizeof(softc); i++) {
        if (rom[i] != 0xCC) {
            printf("  softc->rom+%d (rom[%d]) OVERWRITTEN = 0x%02x\n",
                   i - URTWN_EFUSE_SIZE, i, rom[i]);
            hit = 1;
        }
    }
    if (overflows > 0 && hit)
        printf("RESULT: OVERFLOW CONFIRMED โ€” 8 bytes written %d past sc->rom "
               "into softc tail (kernel: last_rom_addr/callouts/sc_mtx)\n",
               1016 - URTWN_EFUSE_SIZE);
    else if (overflows == 0)
        printf("RESULT: NO OVERFLOW (fix bounds check active)\n");
    else
        printf("RESULT: indexing OOB but canary intact (unexpected)\n");
    return (overflows > 0) ? 1 : 0;
}