DF-3736 / emx_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 | /* * emx_bounds_harness.c - userspace bound-math replica of if_emx.c RX/TX math. * * Pass-2 twin of em_bounds_harness.c (DF-3713/DF-3714), line-for-line * against sys/dev/netif/emx/if_emx.c: * [B] emx_rxeof (:3337-3468) descriptor-length trust: * len = le16toh(rxd_length) at :3369 (16-bit NIC-DMA'd field, * extended RX descriptors, E1000_RFCTL_EXTEN at :3134; * E1000_RCTL_SECRC at :3283 strips the CRC in HW, so unlike * em(4) there is NO -4 adjustment anywhere) -> * mp->m_len = len at :3401, m_pkthdr.len = len / += len at * :3404/:3413, with NO bound against the posted buffer. * Posted buffer: m_getcl() MCLBYTES=2048 cluster (emx_newbuf * :2908-2916), ETHER_ALIGN front-trim iff * hw.mac.max_frame_size <= 2046 (:2918-2919) -> usable 2046/2048. * desc_len = 0xFFFF -> m_len/m_pkthdr.len claim 65535 bytes in a * 2048-byte cluster -> heap OOB read of ~63.5KB into the network * stack (ether_input/BPF/forwarding consumers). * [C] emx_tx_collect (:2808-2868) unvalidated TDH walk math: * tdh = E1000_READ_REG(TDH) at :2817 assigned to *int*; * MMIO read-back of 0xFFFFFFFF (surprise removal / faulty device) * -> tdh = -1; `while (first != tdh)` at :2832 never terminates * (first wraps mod num_tx_desc at :2853-2854 and can never reach * -1 or any value >= num_tx_desc); * KKASSERT(num_avail < num_tx_desc) at :2835 panics INVARIANTS * builds; production builds spin forever holding the TX * serializer, freeing every TX buffer on the first pass * (DMA-after-free window vs. the still-armed device). * * The [A] ETHER_ALIGN-vs-LPE 2-byte DMA overflow case is NOT replicated * here: that is the already-filed DF-1197 (emx) / DF-3712 (em) family and * must not be re-filed in this pass. * * This is *not* a driver exploit - it proves the length math the driver * performs, gated behind hardware the audit guest does not have. */ #include <stdio.h> #include <stdlib.h> /* --- constants mirrored from the kernel/driver --- */ #define MCLBYTES 2048 /* sys/kern/kern_mbuf.c / param.h */ #define ETHER_ALIGN 2 /* net/ethernet.h */ #define ETHERMTU 1500 static int fails; static void check(const char *what, int bad) { printf(" [%s] %s\n", bad ? "OVERFLOW" : "ok ", what); if (bad) fails++; } /* * [B] emx_rxeof:3369-3413 length trust. trim mirrors emx_newbuf:2918. */ static void case_B(int max_frame_size, unsigned desc_len, int eop) { int trim = (max_frame_size <= MCLBYTES - ETHER_ALIGN) ? ETHER_ALIGN : 0; int usable = MCLBYTES - trim; /* DMA-mapped span of cluster */ int len = (int)desc_len; /* emx_rxeof:3369 - used as-is */ printf("[B] max_frame_size=%d (trim=%d, usable=%d), desc_len=%u " "(%s)\n", max_frame_size, trim, usable, desc_len, eop ? "EOP" : "non-EOP fragment"); /* mp->m_len = len (:3401); fmp->m_pkthdr.len =/+= len (:3404/:3413) */ if (len > usable) { printf(" [OVERFLOW] m_len=%d claims %d bytes past the " "%d-byte posted cluster: kernel heap OOB read of up " "to %d bytes per descriptor\n", len, len - usable, usable, len - usable); fails++; } else if (len == 0) { printf(" [note] len==0 also accepted (zero-length mbuf " "into the chain)\n"); } else { check("len within posted span", 0); } } /* * [C] emx_tx_collect:2817-2855 walk math: first advances mod num_tx_desc * until == tdh (raw *int*-narrowed TDH register read). KKASSERT * (num_avail < num_tx_desc) at :2835 is INVARIANTS-only; on production * builds num_avail overflows int after 2^31 iterations. */ static void case_C(int tdh_raw, int ndesc) { int first = 0, num_avail = 36, iters = 0, wraps = 0; int assert_would_fire = 0; long iter_limit = 1 << 22; printf("[C] num_tx_desc=%d, next_tx_to_clean(first)=%d, raw TDH " "register=0x%x -> int tdh=%d\n", ndesc, first, (unsigned)tdh_raw, tdh_raw); if (tdh_raw < 0 || tdh_raw >= ndesc) { while (first != tdh_raw && 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 loop under the TX serializer\n", iters, wraps); check("KKASSERT(num_avail<num_tx_desc) fires on INVARIANTS " "builds (:2835); production: serializer-held forever-" "spin + every tx_buf freed on pass 1", 1); } else { printf(" in-range TDH: normal cleanup, no issue\n"); } } int main(void) { printf("== if_emx.c bound-math harness (guest has no emx hardware; " "source-line replica; [A]=DF-1197 known, excluded) ==\n\n"); printf("--- [B] emx_rxeof descriptor-length trust " "(if_emx.c:3369-3413) ---\n"); case_B(1518, 1500, 1); /* honest std frame: safe */ case_B(1518, 0xFFFF, 0); /* faulty/malicious NIC fragment */ case_B(1518, 0xFFFF, 1); /* faulty/malicious NIC EOP */ case_B(9234, 2048, 0); /* honest jumbo fragment: safe */ case_B(9234, 0xFFFF, 1); /* jumbo config, malicious length */ printf("\n--- [C] emx_tx_collect TDH walk (if_emx.c:2817-2855) ---\n"); case_C(100, 512); /* honest TDH */ case_C(4096, 512); /* out-of-range TDH (e.g. 0x1000) */ case_C(-1, 512); /* TDH register = 0xFFFFFFFF (removal) */ printf("\n%d overflow/spin class(es) demonstrated in bound math\n", fails); return fails ? 1 : 0; } |