DF-3712 / em_bounds_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 | /* * em_bounds_harness.c - userspace bound-math replica of if_em.c RX/TX math. * * Replicates, line-for-line against sys/dev/netif/em/if_em.c (7.6.2 driver): * [A] em_newbuf (:3359-3405) posting math vs em_init_rx_unit (:3593-3635) * RCTL SZ_2048 + LPE configuration -> 2-byte DMA write past cluster * when MTU in (1500, 2028] (ETHER_ALIGN front-trim + long-frame RX). * [B] em_rxeof (:3661-3821) descriptor-length trust: * desc_len = 0xFFFF -> m_len/m_pkthdr.len claim 63,5xx bytes in a * 2048-byte cluster -> heap OOB read of ~63.5KB. * prev_len_adj underflow (:3697-3699, :3754-3758) -> negative m_len. * [C] em_tx_collect (:3275-3335) unvalidated TDH walk math. * * Every allocation is followed by a 64-byte poisoned redzone; any claimed * write/read span past the mapped span hits it. This is *not* a driver * exploit - it proves the length math that the driver performs on the real * kernel heap, gated behind hardware the audit guest does not have. */ #include <stdio.h> #include <stdlib.h> #include <string.h> /* --- constants mirrored from the kernel/driver --- */ #define MCLBYTES 2048 /* sys/sys/param.h */ #define ETHER_ALIGN 2 /* net/ethernet.h */ #define ETHER_CRC_LEN 4 #define ETHERMTU 1500 #define ETHER_HDR_LEN 14 #define REDZONE 64 static unsigned char cluster[MCLBYTES + REDZONE]; static int fails; static void check(const char *what, int bad) { printf(" [%s] %s\n", bad ? "OVERFLOW" : "ok ", what); if (bad) fails++; } /* * [A] em_newbuf:3376-3383 (post) vs em_init_rx_unit:3607-3632 (NIC fill). * max_frame_size = mtu + ETHER_HDR_LEN + ETHER_CRC_LEN (em_ioctl:1276) * ETHER_ALIGN trim iff max_frame_size <= MCLBYTES - ETHER_ALIGN (<=2046) * LPE iff if_mtu > ETHERMTU (em_init_rx_unit:3629) * RCTL_SZ_2048 always (rx_buffer_len==MCLBYTES, set at em_attach:684) * NIC fills first descriptor of a multi-descriptor (jumbo) frame with * min(frame_len, 2048) bytes at the DMA address = cluster start (+trim). */ static void case_A(int mtu, int frame_len) { int max_frame_size = mtu + ETHER_HDR_LEN + ETHER_CRC_LEN; int trim = (max_frame_size <= MCLBYTES - ETHER_ALIGN) ? ETHER_ALIGN : 0; int lpe = mtu > ETHERMTU; int mapped = MCLBYTES - trim; /* bus_dmamap span */ int fill = frame_len < 2048 ? frame_len : 2048; /* RCTL_SZ_2048 */ int dma_end = trim + fill; /* bytes 0..dma_end-1 of cluster */ int oob_write = dma_end > MCLBYTES; printf("[A] MTU=%d (max_frame_size=%d, trim=%d, LPE=%d), frame=%d B, " "mapped=%d B, first-desc fill=%d B -> cluster write span " "[%d, %d)\n", mtu, max_frame_size, trim, lpe, frame_len, mapped, fill, trim, dma_end); if (trim && lpe) { /* the dangerous configuration: trim AND long frames admitted */ memset(cluster, 0xAA, sizeof(cluster)); /* NIC DMA-writes `fill` attacker-frame bytes at cluster+trim */ memset(cluster + trim, 0x41, fill); /* redzone bytes written? (cluster is 2048, zone after it) */ check("2-byte attacker-controlled DMA write past 2048-byte " "mbuf cluster (first descriptor)", oob_write); /* stack side: em_rxeof:3738 sets m_len=len (desc1 non-EOP) */ { int len = fill; /* em_rxeof:3705 */ int m_len_span = trim + len; check("stack-side m_len read span past cluster " "(em_rxeof:3738)", m_len_span > MCLBYTES); } } else { printf(" [skip] trim/LPE do not coexist (safe config)\n"); } } /* * [B] em_rxeof length trust: descriptor says 0xFFFF (16-bit field, * DMA-written by NIC). Non-EOP fragment: len = desc_len (em_rxeof:3705), * mp->m_len = len (:3738); EOP: len = desc_len - ETHER_CRC_LEN (:3701). */ static void case_B(int desc_len, int eop) { int len = eop ? (desc_len < ETHER_CRC_LEN ? 0 : desc_len - ETHER_CRC_LEN) : desc_len; int trim = 0; /* jumbo MTU: no ETHER_ALIGN (em_newbuf) */ int usable = MCLBYTES - trim; printf("[B] desc_len=%u (%s): driver len=%d usable cluster span=%d\n", (unsigned)desc_len, eop ? "EOP" : "non-EOP fragment", len, usable); if (eop && desc_len < ETHER_CRC_LEN) { printf(" (em_rxeof:3697-3699 -> len=0, prev_len_adj=%d - " "DF-1188 territory)\n", ETHER_CRC_LEN - desc_len); return; } if (len > usable) { /* m_len = len; stack/BPF walk len bytes from cluster start */ int oob_read = len - usable; printf(" [OVERFLOW] mp->m_len=%d claims %d bytes past the " "2048-byte cluster: kernel heap OOB read of up to %d " "bytes per descriptor (ether_input/BPF/forwarding " "consumers)\n", len, oob_read, oob_read); fails++; } /* error-descriptor probe: em_rxeof:3715 reads last byte at * mp->m_data + desc_len - 1 */ { int probe = desc_len - 1; if (probe < 0 || probe >= usable) check("error-path last_byte probe OOB (em_rxeof:3715)", probe < 0 || probe >= usable); } } /* * [B2] prev_len_adj underflow: previous non-EOP fragment had m_len 0 * (desc_len=0), EOP fragment desc_len=2 -> prev_len_adj=2 -> * lmp->m_len -= 2 -> NEGATIVE m_len in the chain (em_rxeof:3754-3758). */ static void case_B2(void) { int prev_len = 0; /* previous mp->m_len */ int desc_len = 2; /* EOP fragment < 4 bytes */ int prev_len_adj = ETHER_CRC_LEN - desc_len; /* :3699 */ int new_prev_len = prev_len - prev_len_adj; /* :3755 */ printf("[B2] prev frag m_len=%d, EOP desc_len=%d -> prev_len_adj=%d " "-> lmp->m_len = %d\n", prev_len, desc_len, prev_len_adj, new_prev_len); check("negative m_len injected into RX mbuf chain", new_prev_len < 0); } /* * [C] em_tx_collect:3284-3322 walk math: first advances mod num_tx_desc * until == tdh (raw TDH register read). KKASSERT(num_avail < num_tx_desc) * at :3302 is INVARIANTS-only (sys/sys/systm.h:117-119 no-op otherwise). */ static void case_C(unsigned tdh, int ndesc) { int first = 0, num_avail = 36 /* whatever */, iters = 0, wraps = 0; int assert_would_fire = 0, iter_limit = 1 << 20; printf("[C] num_tx_desc=%d, next_tx_to_clean(first)=%d, raw TDH=%u\n", ndesc, first, tdh); if (tdh >= (unsigned)ndesc) { while (first != (int)tdh && iters < iter_limit) { num_avail++; if (num_avail == ndesc) assert_would_fire = iters + 1; if (++first == ndesc) { first = 0; wraps++; } iters++; } printf(" TDH out of ring range: walked %d descriptors " "(%d full ring wraps) without reaching TDH -> " "unbounded ISR-context loop\n", iters, wraps); check("KKASSERT(num_avail<num_tx_desc) fires on INVARIANTS " "builds (:3302); production: permanent serializer-held " "spin", assert_would_fire != 0); } else { printf(" in-range TDH: normal cleanup, no issue\n"); } } int main(void) { printf("== if_em.c bound-math harness (guest has no em hardware; " "source-line replica) ==\n\n"); printf("--- [A] em_newbuf ETHER_ALIGN trim vs RCTL_SZ_2048+LPE " "(if_em.c:3378-3379, :3607-3632) ---\n"); case_A(1500, 3000); /* standard MTU: safe */ case_A(2000, 3000); /* MTU in (1500,2028]: 2-byte heap overflow */ case_A(9000, 9026); /* big jumbo: no trim, safe from this bug */ printf("\n--- [B] em_rxeof descriptor-length trust (:3692-3761) ---\n"); case_B(1500, 1); /* honest EOP frame: safe */ case_B(0xFFFF, 0); /* malicious/faulty NIC fragment */ case_B(0xFFFF, 1); /* malicious/faulty NIC EOP */ printf("\n--- [B2] prev_len_adj underflow (:3697-3699, :3754-3758) ---\n"); case_B2(); printf("\n--- [C] em_tx_collect TDH walk (:3284-3322) ---\n"); case_C(100, 512); /* honest TDH */ case_C(4096, 512); /* out-of-range TDH */ printf("\n%d overflow class(es) demonstrated in bound math\n", fails); return fails ? 1 : 0; } |