DF-3759 / trigger.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 | /* * DF-3759 trigger - alc(4) RRS frame-length trust (if_alc.c alc_rxeof) * =================================================================== * * HARDWARE PRECONDITION (why this cannot fire on the audit guest): * The trigger is the Atheros/Qualcomm NIC itself. alc_rxeof() * (sys/dev/netif/alc/if_alc.c:3293-3405) consumes an Rx Return * Descriptor (RRS) that the CONTROLLER DMA'd into the return ring: * * status = le32toh(rrd->status); :3302 * rdinfo = le32toh(rrd->rdinfo); :3303 * nsegs = RRD_RD_CNT(rdinfo); 4 bits -> 0..15 :3305 * rxlen = RRD_BYTES(status); 14 bits -> 0..16383 * :3307 (if_alcreg.h:1240-1261) * * and then builds the stack-visible mbuf WITHOUT ANY BOUND on rxlen * against the buffers that actually back the frame (each holds * sc->alc_buf_size == RX_BUF_SIZE_MAX == 1536 bytes, if_alcreg.h:554, * allocated as MCLBYTES clusters in alc_newbuf, if_alc.c:3187-3190): * * m->m_pkthdr.len = rxlen - ETHER_CRC_LEN; :3368 * (nsegs == 1) m->m_len = m->m_pkthdr.len; :3386 * (nsegs > 1) mp->m_len = rxlen - (nsegs-1)*alc_buf_size; :3372 * if (mp->m_len <= ETHER_CRC_LEN) :3375 * rxtail->m_len -= (ETHER_CRC_LEN - mp->m_len):3378 * * On a faulty or malicious controller (or after descriptor corruption): * rxlen=16383, nsegs=1 -> m_len = pkthdr.len = 16379 in a 2048-byte * cluster: every consumer past ether_input (bpf, ip_input, TCP, * socket copyout) reads up to ~14.3KB past the end of the * cluster = kernel heap disclosure to unprivileged sockets. * rxlen=0..3 -> pkthdr.len negative (int) -> mbuf * invariant violation. * nsegs=15, rxlen=100 -> last m_len = 100 - 14*1536 = -21404 -> * the CRC-strip branch drives rxtail->m_len NEGATIVE in the * delivered chain -> panics in mbuf walkers. * * Note the sibling finding DF-1207 covers the ARRAY-INDEX half * (rx_cons/nsegs unvalidated for rxdesc[] indexing, :3304/:3329); * this finding is the LENGTH half (m_len/pkthdr.len vs cluster size) * - same descriptor, distinct sinks, distinct primitive. * * This program models the exact arithmetic of if_alc.c:3368-3386 so the * corrupted lengths can be inspected without hardware, and prints what * the network stack would see. Compile: cc -o trigger trigger.c */ #include <stdio.h> #include <stdint.h> #define ETHER_CRC_LEN 4 #define RX_BUF_SIZE_MAX 1536 /* if_alcreg.h:554 (alc_buf_size) */ #define MCLBYTES 2048 /* cluster actually backing each seg */ struct model { int m_len; /* mbuf->m_len (int) */ int m_pkthdr_len; /* mbuf->m_pkthdr.len (int) */ int cluster; /* real backing bytes per seg */ }; static void rxeof_model(unsigned rxlen, unsigned nsegs, int chain) { int i, pkthdr_len; struct model m[16]; /* :3345 - every chained buffer is assumed full-size */ for (i = 0; i < (int)nsegs; i++) { m[i].m_len = RX_BUF_SIZE_MAX; m[i].cluster = MCLBYTES; } /* :3368 */ pkthdr_len = (int)(rxlen - ETHER_CRC_LEN); if (nsegs > 1) { /* :3372 */ m[nsegs - 1].m_len = (int)(rxlen - (nsegs - 1) * RX_BUF_SIZE_MAX); /* :3375-3384 */ if (m[nsegs - 1].m_len <= ETHER_CRC_LEN) { /* :3376-3380: chop CRC off the PREVIOUS mbuf */ m[nsegs - 2].m_len -= ETHER_CRC_LEN - m[nsegs - 1].m_len; } else { m[nsegs - 1].m_len -= ETHER_CRC_LEN; } } else { /* :3386 */ m[0].m_len = pkthdr_len; } if (chain) { /* print the delivered chain */ for (i = 0; i < (int)nsegs; i++) printf(" m[%d].m_len=%-7d (cluster %d)%s\n", i, m[i].m_len, m[i].cluster, m[i].m_len > m[i].cluster ? " <-- OOB READ BEYOND CLUSTER" : (m[i].m_len < 0 ? " <-- NEGATIVE m_len" : "")); } } int main(void) { printf("DF-3759 model of alc_rxeof (if_alc.c:3368-3386), " "alc_buf_size=%d, cluster=%d\n\n", RX_BUF_SIZE_MAX, MCLBYTES); printf("[leak] rxlen=16383 (RRS 14-bit max), nsegs=1 " "(device-controlled):\n"); rxeof_model(16383, 1, 1); printf(" => pkthdr.len=16379 vs 2048-byte cluster: ~14.3KB heap " "OOB read,\n payload copyable to an unprivileged socket\n\n"); printf("[underflow] rxlen=0, nsegs=1:\n"); rxeof_model(0, 1, 1); printf(" => pkthdr.len=-4 (int underflow)\n\n"); printf("[negative m_len] rxlen=100, nsegs=15:\n"); rxeof_model(100, 15, 1); printf(" => negative m_len survives into the delivered chain; " "mbuf walkers\n (m_pullup/m_adj/m_copydata) violate " "invariants -> panic\n\n"); printf("Real trigger path (needs AR8131/8132/8151/8152/816x/817x " "hardware):\n" " device writes RRS{RRD_VALID, rd_cnt=1, bytes=16383} with no " "error bits\n" " -> alc_rxintr (if_alc.c:3236) -> alc_rxeof (if_alc.c:3293)\n" " -> m handed to if_input (if_alc.c:3400) describing 16379 " "bytes in a\n" " 2048-byte cluster.\n"); return 0; } |