DragonFlyBSD Kernel Audit
DF-0640 / harness.c
← back to finding ↓ download raw
/*
 * DF-0640 harness: ieee80211_crypto_decap minlen check ignores hdrlen
 *
 * Source: sys/netproto/802_11/wlan/ieee80211_crypto.c:584-634
 *
 * The bug:
 *   Line 587-590: IEEE80211_WEP_MINLEN = sizeof(struct ieee80211_frame)=24 +
 *                 IEEE80211_WEP_HDRLEN=4 + IEEE80211_WEP_CRCLEN=4 = 32
 *   Line 598: if (m->m_pkthdr.len < IEEE80211_WEP_MINLEN) return NULL;
 *
 *   But hdrlen (from ieee80211_hdrspace) varies:
 *     - QoS frame: hdrlen = 26 or 28 (with DATAPAD)
 *     - 4-addr frame: hdrlen = 30 or 32
 *
 *   The check uses HARDCODED 24, not the caller-supplied hdrlen.
 *
 * IMPACT 1 (OOB read / panic):
 *   Line 613: m_copydata(m, hdrlen+3, 1, &keyid)
 *   For 4-addr hdrlen=30/32 with m_pkthdr.len in [32, hdrlen+3]:
 *   m_copydata reads past chain end -> KASSERT panic or NULL deref.
 *
 * IMPACT 2 (double-free):
 *   Line 624-625: m_pullup(m, hdrlen+cip->ic_header) for CCMP/TKIP (ic_header=8)
 *   For QoS frame with m_pkthdr.len in [hdrlen+4, hdrlen+7]:
 *   m_copydata succeeds (reads keyid within bounds) but m_pullup fails
 *   (chain too short for hdrlen+8). m_pullup FREES the mbuf and returns NULL.
 *   Function returns NULL. Caller still holds original freed m pointer ->
 *   caller does m_freem(m) -> DOUBLE-FREE.
 *
 * Caller (ieee80211_hostap.c:873): key = ieee80211_crypto_decap(ni, m, hdrspace);
 *   if (key == NULL) goto out;
 *   ...
 *   out: if (m != NULL) m_freem(m);  // DOUBLE-FREE!
 *
 * This requires WiFi hardware (or at least a wlan vap). On QEMU without WiFi,
 * this harness demonstrates the minlen check logic and double-free path.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define IEEE80211_WEP_IVLEN  3
#define IEEE80211_WEP_KIDLEN 1
#define IEEE80211_WEP_HDRLEN (IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN) /* 4 */
#define IEEE80211_WEP_CRCLEN 4

/* sizeof(struct ieee80211_frame) = 24 on this arch */
#define IEEE80211_FRAME_LEN 24

/* The BUGGY macro (hardcodes 24 instead of using hdrlen) */
#define IEEE80211_WEP_MINLEN_BUGGY (IEEE80211_FRAME_LEN + IEEE80211_WEP_HDRLEN + IEEE80211_WEP_CRCLEN) /* 32 */

/* The CORRECT macro (uses hdrlen) */
#define IEEE80211_WEP_MINLEN_FIXED(hdrlen) ((hdrlen) + IEEE80211_WEP_HDRLEN + IEEE80211_WEP_CRCLEN)

/* Simulated mbuf */
struct mbuf {
    int m_len;           /* data length */
    int m_pkthdr_len;    /* total packet length */
    uint8_t *m_data;     /* data pointer */
    int freed;           /* track if mbuf has been freed */
};

/* Replicates m_copydata (simplified) */
static int m_copydata(struct mbuf *m, int off, int len, uint8_t *buf) {
    if (off + len > m->m_pkthdr_len) {
        printf("    m_copydata: OOB! off=%d+len=%d > pkthdr.len=%d\n",
               off, len, m->m_pkthdr_len);
        return -1; /* would panic in kernel */
    }
    /* In kernel with INVARIANTS: KASSERT panic */
    return 0;
}

/* Replicates m_pullup (simplified) */
static struct mbuf *m_pullup(struct mbuf *m, int len) {
    if (m->m_pkthdr_len < len) {
        /* m_pullup fails: FREES the mbuf chain and returns NULL */
        printf("    m_pullup: fails (pkthdr.len=%d < requested=%d) -> FREES mbuf, returns NULL\n",
               m->m_pkthdr_len, len);
        m->freed = 1;
        return NULL;
    }
    return m;
}

/* Replicates ieee80211_crypto_decap (line 584-634) */
static int crypto_decap(struct mbuf *m, int hdrlen) {
    uint8_t keyid;
    int cip_header = 8; /* CCMP/TKIP */

    printf("  crypto_decap(hdrlen=%d, pkthdr.len=%d):\n", hdrlen, m->m_pkthdr_len);

    /* BUGGY minlen check (uses hardcoded 24, not hdrlen) */
    if (m->m_pkthdr_len < IEEE80211_WEP_MINLEN_BUGGY) {
        printf("    minlen check: pkthdr.len=%d < MINLEN=%d -> rejected (correct)\n",
               m->m_pkthdr_len, IEEE80211_WEP_MINLEN_BUGGY);
        return -1; /* correctly rejected */
    }
    printf("    minlen check: pkthdr.len=%d >= MINLEN=%d -> PASSED\n",
           m->m_pkthdr_len, IEEE80211_WEP_MINLEN_BUGGY);

    /* CORRECT minlen check would use hdrlen */
    int correct_min = IEEE80211_WEP_MINLEN_FIXED(hdrlen);
    if (m->m_pkthdr_len < correct_min) {
        printf("    [BUG] CORRECT minlen would be %d (hdrlen=%d) -> SHOULD reject but DIDN'T\n",
               correct_min, hdrlen);
    }

    /* Line 613: m_copydata(m, hdrlen + IEEE80211_WEP_IVLEN, ...) */
    printf("    m_copydata at offset hdrlen+3 = %d\n", hdrlen + 3);
    if (m_copydata(m, hdrlen + IEEE80211_WEP_IVLEN, 1, &keyid) < 0) {
        printf("    -> OOB READ / PANIC!\n");
        return -2; /* OOB panic */
    }

    /* Line 624-625: m_pullup(m, hdrlen + cip->ic_header) */
    printf("    m_pullup for hdrlen+cip_header = %d + %d = %d\n",
           hdrlen, cip_header, hdrlen + cip_header);
    struct mbuf *m2 = m_pullup(m, hdrlen + cip_header);
    if (m2 == NULL) {
        printf("    -> m_pullup failed, mbuf FREED, function returns NULL\n");
        printf("    -> CALLER still holds original m -> m_freem(m) -> DOUBLE-FREE!\n");
        return -3; /* double-free */
    }

    return 0;
}

int main(void) {
    printf("=== DF-0640: ieee80211_crypto_decap minlen ignores hdrlen ===\n\n");
    printf("NOTE: Requires WiFi hardware — bug confirmed by source trace.\n");
    printf("      Harness demonstrates the minlen bypass and double-free path.\n\n");

    printf("IEEE80211_WEP_MINLEN (buggy) = %d (hardcodes sizeof(ieee80211_frame)=%d)\n\n",
           IEEE80211_WEP_MINLEN_BUGGY, IEEE80211_FRAME_LEN);

    /* Test case 1: QoS frame, hdrlen=26, pkthdr.len=34 */
    /* Buggy check: 34 >= 32 -> passes. Correct: 34 >= 34 -> barely passes */
    printf("[Test 1] QoS frame (hdrlen=26, len=34):\n");
    struct mbuf m1 = {.m_pkthdr_len = 34, .m_len = 34, .freed = 0};
    crypto_decap(&m1, 26);
    printf("\n");

    /* Test case 2: 4-addr QoS frame, hdrlen=32, pkthdr.len=33 */
    /* Buggy check: 33 >= 32 -> passes! Correct: 33 >= 40 -> should reject */
    /* m_copydata at offset 35 > 33 -> OOB read / panic */
    printf("[Test 2] 4-addr QoS frame (hdrlen=32, len=33):\n");
    printf("  -> Buggy check passes (33 >= 32), correct would reject (33 < 40)\n");
    struct mbuf m2 = {.m_pkthdr_len = 33, .m_len = 33, .freed = 0};
    int result = crypto_decap(&m2, 32);
    if (result == -2)
        printf("  -> IMPACT 1: OOB read -> kernel panic\n\n");

    /* Test case 3: QoS frame, hdrlen=26, pkthdr.len=30 */
    /* Buggy: 30 >= 32 -> FAILS. But what about len=33? */
    /* Buggy: 33 >= 32 passes. Correct: 33 >= 34 fails. */
    /* m_copydata at 29 succeeds (29+1=30 <= 33). */
    /* m_pullup(26+8=34): 33 < 34 -> fails -> FREES mbuf -> double-free */
    printf("[Test 3] QoS frame (hdrlen=26, len=33):\n");
    printf("  -> Buggy check passes (33 >= 32), correct would reject (33 < 34)\n");
    struct mbuf m3 = {.m_pkthdr_len = 33, .m_len = 33, .freed = 0};
    result = crypto_decap(&m3, 26);
    if (result == -3)
        printf("  -> IMPACT 2: DOUBLE-FREE (caller will m_freem the freed mbuf)\n\n");

    printf("=== SUMMARY ===\n");
    printf("hdrlen from ieee80211_hdrspace varies (QoS=26, 4-addr=30/32).\n");
    printf("Buggy MINLEN uses hardcoded 24, admitting frames too short for ops.\n");
    printf("Impact 1: m_copydata OOB -> panic (INVARIANTS) or NULL deref\n");
    printf("Impact 2: m_pullup fail -> mbuf freed -> caller double-free\n");
    printf("         (mbuf double-free is a well-known code-exec primitive)\n");
    printf("Fix: use hdrlen in the minlen check.\n");

    return 0;
}