DF-3958 / 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 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 | /* DF-3958 โ vtnet mergeable-RX sub-buffer negative device length. * * Function-level replica of the EXACT kernel code path: * - virtqueue_dequeue() writes the device-controlled u32 used-ring length * through a `uint32_t *lenp' (sys/dev/virtual/virtio/virtio/virtqueue.c:544-561) * - if_vtnet.c passes `int len' to it (if_vtnet.c:1642,1649) โ the pointer * type mismatch is the same one behind DF-1310, but the mergeable * sub-buffer path clamps with a SIGNED compare: * * if (m->m_len < len) // if_vtnet.c:1663 โ signed min() * len = m->m_len; * m->m_len = len; // if_vtnet.c:1666 โ m_len := negative * m_head->m_pkthdr.len += len; // if_vtnet.c:1669 โ pkthdr.len corrupted * * A device length >= 0x80000000 becomes a negative int, the clamp is * skipped (m->m_len < len is false for negative len), and an mbuf with a * NEGATIVE m_len is chained onto m_head and handed to ifp->if_input() * (if_vtnet.c:1670-1671, 1777) BEFORE any length validation โ the chain * reaches bridge/carp/ng_ether taps and BPF (sys/net/if_ethersubr.c:1212+). * * Builds and runs identically on DragonFly guest (cc -O2 -Wall). */ #include <stdio.h> #include <stdint.h> #include <limits.h> #include <string.h> #define ETHER_HDR_LEN 14 #define VTNET_MAX_RX_SIZE 65550 /* --- kernel type model (int fields exactly as sys/mbuf.h) --- */ struct mbuf { int m_len; /* int in kernel */ int m_pkthdr_len; /* int in kernel */ struct mbuf *m_next; }; /* --- replica of virtqueue_dequeue()'s length store (virtqueue.c:560-561): * `*len = uep->len' where the callee writes uint32_t and the vtnet * caller (if_vtnet.c:1649) owns an `int len'. Model the exact store. */ static void vq_dequeue_store_len(void *lenp_int, uint32_t used_ring_len) { *(uint32_t *)lenp_int = used_ring_len; } /* sub-buffer mbuf that survives the replica call (kernel mbufs are heap) */ static struct mbuf sub_m; /* --- replica of vtnet_rxeof_merged() core (if_vtnet.c:1648-1672) --- */ static int rxeof_merged_vuln(struct mbuf *m_head, uint32_t sub_used_len) { struct mbuf *m, *m_tail; int len; m_tail = m_head; /* while (--nbufs > 0) { m = virtqueue_dequeue(vq, &len); ... } */ memset(&sub_m, 0, sizeof(sub_m)); sub_m.m_len = 2048; /* clsize at alloc (if_vtnet.c:1239) */ m = &sub_m; vq_dequeue_store_len(&len, sub_used_len); /* device-controlled */ if (m->m_len < len) /* if_vtnet.c:1663 (signed) */ len = m->m_len; m->m_len = len; /* if_vtnet.c:1666 */ m_head->m_pkthdr_len += len; /* if_vtnet.c:1669 */ m_tail->m_next = m; /* if_vtnet.c:1670 */ return (0); } /* --- replica of the FIXED clamp: unsigned compare (see fix.diff) --- */ static int rxeof_merged_fixed(struct mbuf *m_head, uint32_t sub_used_len) { struct mbuf *m; int len; memset(&sub_m, 0, sizeof(sub_m)); sub_m.m_len = 2048; m = &sub_m; vq_dequeue_store_len(&len, sub_used_len); /* FIX if_vtnet.c:1663: treat the device length as unsigned โ any * insane (>= 0x80000000 or > buffer) length is clamped to the * buffer's m_len, exactly as the virtio device is supposed to behave. */ if ((uint32_t)len > (uint32_t)m->m_len) len = m->m_len; m->m_len = len; m_head->m_pkthdr_len += len; m_head->m_next = m; return (0); } /* --- downstream consumer replicas that walk the corrupted chain --- */ /* sys/kern/uipc_mbuf.c:1686-1695 m_copydata() second loop: * count = min(m->m_len - off, len); bcopy(..., count); * with off==0 โ for a negative m_len, count goes negative and is passed * to bcopy() as an unsigned byte count. */ static int m_copydata_bcopy_count(struct mbuf *m) { int len = 32, off = 0; int count = (m->m_len - off) < len ? (m->m_len - off) : len; return count; } int main(void) { struct mbuf m_head; uint32_t dev_len = 0x80000000u; /* >= 0x80000000 from device */ int i; struct { int m_len; } chains[4] = { { 14 }, { 100 }, { 1000 }, { 2048 } }; printf("== DF-3958 vtnet_rxeof_merged signed sub-buffer length ==\n"); printf("device used-ring len = 0x%08x\n", dev_len); memset(&m_head, 0, sizeof(m_head)); m_head.m_len = 2048; /* first buffer, >= hdr+14 */ m_head.m_pkthdr_len = 26; /* hdr_size(12)+ETHER_HDR_LEN(14) */ rxeof_merged_vuln(&m_head, dev_len); printf("-- BEFORE-FIX (kernel code as-is) --\n"); printf("m_head.m_pkthdr.len = %d (<- 26 + negative)\n", m_head.m_pkthdr_len); printf("m2->m_len = %d\n", m_head.m_next->m_len); if (m_head.m_next->m_len < 0) printf("BUG: mbuf with NEGATIVE m_len chained & delivered to " "if_input (if_vtnet.c:1670,1777)\n"); i = m_copydata_bcopy_count(m_head.m_next); printf("m_copydata(m2,...): count=min(m_len,len)=%d -> " "(unsigned)0x%x -> bcopy of %u bytes => panic/smash " "(uipc_mbuf.c:1689-1690)\n", i, (unsigned)i, (unsigned)i); printf("bpf_mcopy: walks into m2 data area reading stale cluster " "bytes into listener buffer (bpf.c:1280-1284)\n"); printf("first-buffer guards do NOT protect sub-buffers: " "if_vtnet.c:1707 len<%d check and :1964 clamp only run on the " "FIRST buffer\n", 12 + ETHER_HDR_LEN); /* sanity sweep: a range of negative-producing device lengths */ printf("-- sweep (first-buffer len 26, sub len 0x80000000+k) --\n"); for (i = 0; i < 4; i++) { struct mbuf h = { .m_len = 2048, .m_pkthdr_len = 26 }; rxeof_merged_vuln(&h, 0x80000000u + (uint32_t)chains[i].m_len); printf("sub_len=0x8000%04x -> m2->m_len=%d pkthdr.len=%d\n", chains[i].m_len, h.m_next->m_len, h.m_pkthdr_len); } memset(&m_head, 0, sizeof(m_head)); m_head.m_len = 2048; m_head.m_pkthdr_len = 26; printf("-- AFTER-FIX (unsigned clamp, fix.diff) --\n"); rxeof_merged_fixed(&m_head, dev_len); printf("m2->m_len=%d (clamped to buffer m_len), pkthdr.len=%d โ " "sane chain, normal delivery\n", m_head.m_next->m_len, m_head.m_pkthdr_len); return (m_head.m_next ? 0 : 1); } |