DF-0616 / df0616_harness.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | /* * 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; } |