โฌข DragonFlyBSD Kernel Audit
DF-0616 / df0616_harness.c
โ† back to finding โ†“ download raw
/*
 * DF-0616 โ€” Code-level proof: heap buffer overflow in generic_netmap_rxsync().
 *
 * The runtime netmap path is unavailable on this master-DEV guest:
 *   - netmap is NOT compiled into the X86_64_GENERIC kernel
 *     (grep NETMAP sys/config/X86_64_GENERIC -> empty), and
 *   - the netmap KLD module no longer compiles against master
 *     (struct ifnet dropped the `if_unused7` slot that netmap_kern.h:747
 *      `WNA()` relies on โ€” only if_unused2/if_unused4 remain in if_var.h),
 *   so no NIC can be placed in netmap mode on this guest, and QEMU SLIRP
 *     user-mode networking caps the path MTU at 1500 (no jumbo/LRO delivery).
 *
 * Per the DF-0265/DF-0594 precedent, this harness reproduces the bug by
 * replicating the EXACT logic of the vulnerable path:
 *   - MBUF_LEN()           verbatim from sys/net/netmap/netmap_kern.h:52
 *   - m_copydata()         verbatim from sys/kern/uipc_mbuf.c:1671-1696
 *                           (KASSERTs elided; bcopy -> memcpy; semantics identical)
 *   - the RX copy loop      verbatim from sys/net/netmap/netmap_generic.c:669-674
 * The destination `addr` models a netmap BUF_POOL object, which is fixed at
 * 2048 bytes (NETMAP_BUF_POOL.size, netmap_mem2.c:765). A >2048-byte RX mbuf
 * (jumbo frame / LRO aggregation) is fed in; the unbounded m_copydata writes
 * past the 2048-byte buffer into an adjacent canary region that models the
 * neighboring netmap buffers / kernel heap.
 *
 * Build (bug present):
 *     cc -O2 -Wall -o df0616_harness df0616_harness.c
 * Build (proposed fix applied โ€” mirrors netmap_generic.c:500 TX-side check):
 *     cc -O2 -Wall -DFIX -o df0616_harness_fixed df0616_harness.c
 * Run:
 *     ./df0616_harness 9000        # 9000-byte jumbo frame
 *     ./df0616_harness 65535       # max LRO-aggregated frame
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* ---- constants taken verbatim from the audited kernel sources ---- */
#define NETMAP_BUF_SIZE   2048    /* netmap_mem2.c:765  p[NETMAP_BUF_POOL].size = 2048 */
#define CANARY_SIZE       8192    /* models adjacent netmap buffers / kernel heap */
#define CANARY_BYTE       0xAA

/* ---- minimal mbuf model (only the fields the copy reads) ---- */
struct mbuf {
    struct mbuf *m_next;
    char        *m_data;
    int          m_len;
    struct { int len; } m_pkthdr;
};
/* netmap_kern.h:52 */ #define MBUF_LEN(m)  ((m)->m_pkthdr.len)

/*
 * m_copydata โ€” verbatim copy of sys/kern/uipc_mbuf.c:1671-1696.
 * KASSERTs elided; bcopy() replaced by memcpy() (identical byte semantics).
 * NOTE: like the kernel original, this function has NO knowledge of the
 * destination buffer size โ€” it copies exactly `len` bytes regardless.
 */
static void
m_copydata(const struct mbuf *m, int off, int len, void *_cp)
{
    caddr_t cp = _cp;          /* kernel uses caddr_t; char* here */
    unsigned count;

    while (off > 0) {
        if (off < m->m_len)
            break;
        off -= m->m_len;
        m = m->m_next;
    }
    while (len > 0) {
        count = (unsigned)(m->m_len - off);
        if (count > (unsigned)len)
            count = (unsigned)len;        /* min(m->m_len - off, len) */
        memcpy(cp, m->m_data + off, count);   /* kernel: bcopy(...) */
        len -= count;
        cp += count;
        off = 0;
        m = m->m_next;
    }
}

/* Build an mbuf chain of `total` bytes filled with an attacker-recognizable
 * pattern so that OOB writes are visibly attacker-controlled (not noise). */
static struct mbuf *
make_chain(int total)
{
    struct mbuf *head = NULL, *tail = NULL;
    int remaining = total;
    head = NULL;
    while (remaining > 0) {
        int chunk = remaining > 2048 ? 2048 : remaining;
        struct mbuf *m = calloc(1, sizeof(*m));
        int i;
        m->m_data = malloc(chunk);
        for (i = 0; i < chunk; i++)
            m->m_data[i] = (char)(0x41 + (i % 26));   /* 'A','B',... attacker frame */
        m->m_len  = chunk;
        m->m_next = NULL;
        if (!head) {
            head = m;
            head->m_pkthdr.len = total;   /* set once on the packet header mbuf */
        } else {
            tail->m_next = m;
        }
        tail = m;
        remaining -= chunk;
    }
    return head;
}

int
main(int argc, char **argv)
{
    int frame_len = (argc > 1) ? atoi(argv[1]) : 9000;
    int total = NETMAP_BUF_SIZE + CANARY_SIZE;
    char *pool, *addr;
    struct mbuf *m;
    int len, oob, first_oob, overflow_len, i;

    printf("=== DF-0616 code-level harness: generic_netmap_rxsync OOB write ===\n");
    printf("[*] NETMAP_BUF_SIZE   = %d  (netmap_mem2.c:765)\n", NETMAP_BUF_SIZE);
    printf("[*] RX frame m_pkthdr.len = %d  (jumbo/LRO frame)\n", frame_len);
#ifdef FIX
    printf("[*] FIX ENABLED: len clamped to NETMAP_BUF_SIZE before m_copydata "
           "(mirrors netmap_generic.c:500)\n");
#else
    printf("[*] FIX DISABLED: reproducing vulnerable netmap_generic.c:672-674\n");
#endif

    if (frame_len <= 0) {
        fprintf(stderr, "[-] invalid frame length\n");
        return 1;
    }

    /* model the netmap BUF_POOL: the slot-j buffer is 2048 bytes; the bytes
     * after it model the adjacent pool objects the overflow corrupts. */
    pool = malloc(total);
    if (!pool) { perror("malloc"); return 1; }
    memset(pool, CANARY_BYTE, total);
    addr = pool;                      /* NMB(&ring->slot[j]) -> 2048-byte buffer */

    m = make_chain(frame_len);
    len = MBUF_LEN(m);                /* netmap_generic.c:672 */

#ifdef FIX
    /* proposed fix (mirrors TX-side check at netmap_generic.c:500): */
    if (len > NETMAP_BUF_SIZE) {
        printf("[*] FIX: len %d > NETMAP_BUF_SIZE %d -> clamping to %d\n",
               len, NETMAP_BUF_SIZE, NETMAP_BUF_SIZE);
        len = NETMAP_BUF_SIZE;
    }
#endif

    /* the vulnerable call (netmap_generic.c:673): */
    printf("[*] m_copydata(m, 0, len=%d, addr) into %d-byte netmap buffer\n",
           len, NETMAP_BUF_SIZE);
    m_copydata(m, 0, len, addr);
    /* ring->slot[j].len = len;  (netmap_generic.c:674) */

    /* measure the out-of-bounds write into the adjacent canary region */
    oob = 0;
    first_oob = -1;
    for (i = NETMAP_BUF_SIZE; i < total; i++) {
        if ((unsigned char)pool[i] != CANARY_BYTE) {
            if (first_oob < 0)
                first_oob = i;
            oob++;
        }
    }

    printf("\n--- RESULT ---\n");
    if (oob == 0) {
        printf("[+] No OOB write: all %d bytes stayed within the %d-byte buffer.\n",
               len, NETMAP_BUF_SIZE);
        printf("[+] ring->slot[j].len = %d\n", len);
#ifdef FIX
        printf("[+] FIX HOLDS: overflow prevented (clamped).\n");
        return 0;
#else
        /* without FIX, a >2048 frame MUST overflow; reaching here means the
         * frame was <=2048 and there is nothing to prove. */
        printf("[*] (frame <= NETMAP_BUF_SIZE; no overflow expected)\n");
        return 0;
#endif
    }

    overflow_len = (len > NETMAP_BUF_SIZE) ? (len - NETMAP_BUF_SIZE) : 0;
    printf("[!] OOB WRITE CONFIRMED: %d bytes corrupted past the %d-byte buffer\n",
           oob, NETMAP_BUF_SIZE);
    printf("[!] First corrupted byte: pool[%d] (= +%d past buffer end)\n",
           first_oob, first_oob - NETMAP_BUF_SIZE);
    printf("[!] Overflow extent (frame_len - NETMAP_BUF_SIZE) = %d bytes\n",
           overflow_len);
    printf("[!] Corrupted adjacent-pool bytes are ATTACKER-CONTROLLED "
           "(0x41+ pattern, canary was 0x%02X):\n    ", CANARY_BYTE);
    for (i = NETMAP_BUF_SIZE; i < NETMAP_BUF_SIZE + 32 && i < total; i++)
        printf("%02x ", (unsigned char)pool[i]);
    printf("\n");
    printf("[!] ring->slot[j].len = %d (oversized -> also leaks len to userspace)\n",
           len);
    printf("[!] Impact: CWE-787 OOB write into shared netmap pool / kernel heap\n");
    printf("[!]        => memory corruption, info leak, kernel panic / LPE\n");
    return 0;
}