DF-0743 / 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 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 | /* * DF-0743 โ greip / mobip_h overlay assumes ip_hl == 5 * ===================================================== * * Deterministic userspace harness that replicates the EXACT struct-overlay * dereferences performed by the DragonFlyBSD kernel in: * * sys/netinet/ip_gre.c : gre_input2() (IPPROTO_GRE) * sys/netinet/ip_gre.c : gre_mobile_input() (IPPROTO_MOBILE) * * Both handlers overlay a fixed-layout struct (struct greip / struct mobip_h) * at the very start of the mbuf (mtod(m)) and dereference the GRE / mobile * fields at a FIXED byte offset that corresponds to a 20-byte (ip_hl==5) IP * header: * * struct greip { struct ip gi_i; struct gre_h gi_g; } * gi_flags == gi_g.flags -> byte offset 20 (sizeof(struct ip)) * gi_ptype == gi_g.ptype -> byte offset 22 * * struct mobip_h { struct ip mi; struct mobile_h mh; } * mh.proto -> byte offset 20 * mh.odst -> byte offset 24 * mh.osrc -> byte offset 28 * * When the OUTER encapsulating IP header carries IP options (ip_hl > 5), the * real GRE / mobile header begins at byte offset ip_hl*4 > 20, but the overlay * still dereferences offset 20 / 22 / 24 / 28 โ i.e. it reads the IP-OPTION * bytes, not the GRE / mobile header. This is a deterministic misparse. * * (The pointer-arithmetic strip that follows โ m->m_data += hlen where * hlen = ip_hl*4 + sizeof(gre_h), computed from the *offp argument passed by * ip_input โ DOES correctly account for IP options. Only the struct-overlay * field reads are wrong. So the bytes that drive the protocol-decision logic * [flags -> optional-field sizing; ptype -> inner-protocol classification; * S-bit -> mobile-header size; osrc/odst -> rewritten inner src/dst; * gre_in_cksum range -> checksum validation] all come from the wrong place.) * * This harness constructs an mbuf-shaped buffer that mimics exactly what the * kernel sees when ip_input delivers a GRE/mobile packet with ip_hl==7 * (28-byte outer IP header = 20-byte fixed part + 8 bytes of options) to the * gre handlers, then performs the kernel's overlay derefs byte-for-byte and * compares them with the CORRECT (ip_hl-aware) reads. On the vulnerable * logic the two differ for EVERY field => the misparse is proven. * * Build: cc -O2 -Wall -o harness harness.c * Run: ./harness (no privilege required; pure C replication) * * Expected on vulnerable logic (this finding): the "OVERLAY" reads return the * IP-option bytes (0xAAAA.. / 0xBBBB.. / 0xCCCC.. / 0xDDDD..) and DIFFER from * the "CORRECT" reads (the real GRE/mobile header bytes) for every field => * harness prints "MISPARSE PROVEN (vulnerable)". * * After the fix (overlay pointer computed from ip_hl*4, or fields read via an * explicit offset), the kernel reads would equal the "CORRECT" reads => * equivalent harness logic would print "no misparse". */ #include <sys/types.h> #include <stdio.h> #include <stdint.h> #include <string.h> #include <stdlib.h> #include <arpa/inet.h> /* ---- Mirror the kernel's struct layouts (sys/net/gre/if_gre.h) ---- */ struct ip_hdr { /* 20-byte fixed part */ uint8_t ip_v_hl; /* version<<4 | header_len>>2 */ uint8_t ip_tos; uint16_t ip_len; uint16_t ip_id; uint16_t ip_off; uint8_t ip_ttl; uint8_t ip_p; uint16_t ip_sum; uint32_t ip_src; uint32_t ip_dst; } __attribute__((__packed__)); struct gre_h { /* sys/net/gre/if_gre.h:65 */ uint16_t flags; uint16_t ptype; } __attribute__((__packed__)); struct greip { /* sys/net/gre/if_gre.h:90 */ struct ip_hdr gi_i; struct gre_h gi_g; } __attribute__((__packed__)); #define gi_ptype gi_g.ptype #define gi_flags gi_g.flags struct mobile_h { /* sys/net/gre/if_gre.h:134 */ uint16_t proto; uint16_t hcrc; uint32_t odst; uint32_t osrc; } __attribute__((__packed__)); struct mobip_h { /* sys/net/gre/if_gre.h:141 */ struct ip_hdr mi; struct mobile_h mh; } __attribute__((__packed__)); #define MOB_H_SBIT 0x0080 /* IP header length (bytes) encoded in ip_v_hl as the kernel reads it. */ #define IP_HL(ip) (((ip)->ip_v_hl) & 0x0f) static uint16_t cksum16(const void *p, size_t len) { uint32_t s = 0; const uint16_t *w = p; int n = len >> 1; while (n-- > 0) s += *w++; if (len & 1) s += htons(((const uint8_t *)p)[len - 1]); s = (s >> 16) + (s & 0xffff); s += (s >> 16); return (uint16_t)(~s); } static int fail = 0; static void check_field(const char *name, uint16_t overlay, uint16_t correct) { int bad = (overlay != correct); if (bad) fail = 1; printf(" %-22s overlay=0x%04x correct=0x%04x %s\n", name, overlay, correct, bad ? "*** MISMATCH (misparse) ***" : "ok"); } static void check_field32(const char *name, uint32_t overlay, uint32_t correct) { int bad = (overlay != correct); if (bad) fail = 1; printf(" %-22s overlay=0x%08x correct=0x%08x %s\n", name, overlay, correct, bad ? "*** MISMATCH (misparse) ***" : "ok"); } /* ====================== GRE path (gre_input2) ====================== */ static void test_gre(void) { /* Build an mbuf-shaped buffer: 28-byte IP header (ip_hl=7, 8 opt bytes) * followed by the REAL gre header at offset 28, then inner payload. */ uint8_t buf[64]; memset(buf, 0, sizeof(buf)); /* Outer IP header, ip_hl = 7 (28 bytes). */ struct ip_hdr *ip = (struct ip_hdr *)buf; ip->ip_v_hl = (4 << 4) | 7; /* version 4, ip_hl 7 */ ip->ip_len = htons(sizeof(buf)); ip->ip_ttl = 64; ip->ip_p = 47; /* IPPROTO_GRE */ ip->ip_src = htonl(0x7f000001); /* 127.0.0.1 */ ip->ip_dst = htonl(0x7f000001); /* IP checksum over the full 28-byte header (options included). */ ip->ip_sum = 0; ip->ip_sum = cksum16(buf, 28); /* 8 bytes of IP options at offset 20..27 โ DISTINCT marker bytes so a * misparse is unambiguous. */ buf[20] = 0xAA; buf[21] = 0xAA; /* would-be gi_flags */ buf[22] = 0xBB; buf[23] = 0xBB; /* would-be gi_ptype */ buf[24] = 0x0c; buf[25] = 0x04; /* would-be mh.odst hi */ buf[26] = 0x00; buf[27] = 0x00; /* (checksum already computed over these; recompute to stay consistent) */ ip->ip_sum = 0; ip->ip_sum = cksum16(buf, 28); /* REAL gre header at offset 28 (ip_hl*4). */ struct gre_h *real = (struct gre_h *)(buf + 28); real->flags = htons(0x0000); real->ptype = htons(0x0800); /* ETHERTYPE_IP */ printf("=== GRE path (gre_input2) outer ip_hl=%d (IP options present) ===\n", IP_HL(ip)); printf(" IP-option bytes @20..27: " "%02x %02x %02x %02x %02x %02x %02x %02x\n", buf[20], buf[21], buf[22], buf[23], buf[24], buf[25], buf[26], buf[27]); printf(" real GRE header @28..31: " "flags=0x%04x ptype=0x%04x\n", ntohs(real->flags), ntohs(real->ptype)); /* ---- Replicate the kernel's overlay read (gre_input2, ip_gre.c:133) ---- * struct greip *gip = mtod(m, struct greip *); * flags = ntohs(gip->gi_flags); // ip_gre.c:151 * switch (ntohs(gip->gi_ptype)) { // ip_gre.c:164 */ struct greip *gip = (struct greip *)buf; /* mtod(m) equivalent */ uint16_t k_flags = ntohs(gip->gi_flags); /* fixed offset 20 */ uint16_t k_ptype = ntohs(gip->gi_ptype); /* fixed offset 22 */ /* ---- Correct (ip_hl-aware) read: gre header starts at ip_hl*4 ---- */ struct gre_h *gh = (struct gre_h *)(buf + (IP_HL(ip) << 2)); uint16_t c_flags = ntohs(gh->flags); uint16_t c_ptype = ntohs(gh->ptype); check_field("gi_flags", k_flags, c_flags); check_field("gi_ptype", k_ptype, c_ptype); /* Demonstrate the protocol-decision divergence: kernel's overlay ptype * (0xBBBB) is NOT ETHERTYPE_IP -> switch default -> return(0) -> packet * silently dropped. Correct ptype (0x0800) -> accepted -> decapsulated. */ int k_accept = (k_ptype == 0x0800); int c_accept = (c_ptype == 0x0800); printf(" kernel overlay decision: ptype 0x%04x %s ETHERTYPE_IP -> %s\n", k_ptype, k_accept ? "==" : "!=", k_accept ? "ACCEPT/decapsulate" : "DROP"); printf(" correct decision: ptype 0x%04x %s ETHERTYPE_IP -> %s\n", c_ptype, c_accept ? "==" : "!=", c_accept ? "ACCEPT/decapsulate" : "DROP"); if (k_accept != c_accept) { fail = 1; printf(" *** DECISION DIVERGES: misparse changes accept/drop ***\n"); } printf("\n"); } /* ================== MOBILE path (gre_mobile_input) ================= */ static void test_mobile(void) { uint8_t buf[64]; memset(buf, 0, sizeof(buf)); struct ip_hdr *ip = (struct ip_hdr *)buf; ip->ip_v_hl = (4 << 4) | 7; /* ip_hl = 7 */ ip->ip_len = htons(sizeof(buf)); ip->ip_ttl = 64; ip->ip_p = 55; /* IPPROTO_MOBILE */ ip->ip_src = htonl(0x7f000001); ip->ip_dst = htonl(0x7f000001); /* 8 distinct option-marker bytes at 20..27 */ buf[20] = 0xCC; buf[21] = 0xCC; /* would-be mh.proto */ buf[22] = 0x00; buf[23] = 0x00; /* would-be mh.hcrc */ buf[24] = 0xDD; buf[25] = 0xDD; /* would-be mh.odst */ buf[26] = 0xDD; buf[27] = 0xDD; ip->ip_sum = 0; ip->ip_sum = cksum16(buf, 28); /* REAL mobile header at offset 28 (ip_hl*4), S-bit set, valid checksum. */ struct mobile_h *mh = (struct mobile_h *)(buf + 28); mh->proto = htons(MOB_H_SBIT); /* S-bit set -> msiz = 12 */ mh->hcrc = 0; mh->odst = htonl(0x0a000002); /* 10.0.0.2 */ mh->osrc = htonl(0x0a000003); /* 10.0.0.3 */ mh->hcrc = cksum16(mh, sizeof(*mh)); /* valid gre_in_cksum */ printf("=== MOBILE path (gre_mobile_input) outer ip_hl=%d ===\n", IP_HL(ip)); printf(" IP-option bytes @20..27: " "%02x %02x %02x %02x %02x %02x %02x %02x\n", buf[20], buf[21], buf[22], buf[23], buf[24], buf[25], buf[26], buf[27]); printf(" real mobile hdr @28..39: proto=0x%04x hcrc=0x%04x " "odst=0x%08x osrc=0x%08x\n", ntohs(mh->proto), ntohs(mh->hcrc), ntohl(mh->odst), ntohl(mh->osrc)); /* ---- Kernel overlay read (gre_mobile_input, ip_gre.c:210) ---- * struct mobip_h *mip = mtod(m, struct mobip_h *); * if (ntohs(mip->mh.proto) & MOB_H_SBIT) { // :223 * msiz = MOB_H_SIZ_L; * mip->mi.ip_src.s_addr = mip->mh.osrc; // :225 * } else msiz = MOB_H_SIZ_S; * mip->mi.ip_dst.s_addr = mip->mh.odst; // :229 * mip->mi.ip_p = (ntohs(mip->mh.proto) >> 8); // :230 */ struct mobip_h *mip = (struct mobip_h *)buf; /* mtod(m) equiv */ uint16_t k_proto = ntohs(mip->mh.proto); /* fixed offset 20 */ uint32_t k_odst = mip->mh.odst; /* fixed offset 24 */ uint32_t k_osrc = mip->mh.osrc; /* fixed offset 28 */ /* ---- Correct (ip_hl-aware) read ---- */ struct mobile_h *mh_c = (struct mobile_h *)(buf + (IP_HL(ip) << 2)); uint16_t c_proto = ntohs(mh_c->proto); uint32_t c_odst = mh_c->odst; uint32_t c_osrc = mh_c->osrc; check_field("mh.proto", k_proto, c_proto); check_field32("mh.odst", k_odst, c_odst); check_field32("mh.osrc", k_osrc, c_osrc); int k_sbit = (k_proto & MOB_H_SBIT) != 0; int c_sbit = (c_proto & MOB_H_SBIT) != 0; printf(" kernel overlay S-bit: proto 0x%04x -> S=%d -> msiz=%d\n", k_proto, k_sbit, k_sbit ? 12 : 8); printf(" correct S-bit: proto 0x%04x -> S=%d -> msiz=%d\n", c_proto, c_sbit, c_sbit ? 12 : 8); if (k_sbit != c_sbit) { fail = 1; printf(" *** S-BIT DECISION DIVERGES: misparse changes msiz ***\n"); } /* gre_in_cksum is computed over &mip->mh (offset 20) for msiz bytes โ on * the overlay that is the OPTION bytes, so the checksum fails and the * packet is dropped at ip_gre.c:232-234 even though the real mobile * header has a VALID checksum. */ uint16_t k_cksum = cksum16(&mip->mh, k_sbit ? 12 : 8); uint16_t c_cksum = cksum16(mh_c, c_sbit ? 12 : 8); printf(" kernel overlay gre_in_cksum(&mip->mh) = 0x%04x -> %s\n", k_cksum, k_cksum == 0 ? "PASS" : "FAIL -> m_freem DROP"); printf(" correct gre_in_cksum(mh_real) = 0x%04x -> %s\n", c_cksum, c_cksum == 0 ? "PASS" : "FAIL -> m_freem DROP"); if ((k_cksum == 0) != (c_cksum == 0)) { fail = 1; printf(" *** CHECKSUM DECISION DIVERGES ***\n"); } printf("\n"); } int main(void) { printf("DF-0743 harness: greip / mobip_h overlay vs ip_hl-aware read\n"); printf("Replicates the kernel struct-overlay derefs at:\n"); printf(" sys/netinet/ip_gre.c:133 (gip = mtod(m, struct greip *))\n"); printf(" sys/netinet/ip_gre.c:151 (flags = ntohs(gip->gi_flags))\n"); printf(" sys/netinet/ip_gre.c:164 (ntohs(gip->gi_ptype))\n"); printf(" sys/netinet/ip_gre.c:210 (mip = mtod(m, struct mobip_h *))\n"); printf(" sys/netinet/ip_gre.c:223,225,229,230,232\n\n"); test_gre(); test_mobile(); if (fail) { printf("VERDICT: MISPARSE PROVEN (vulnerable) โ overlay reads IP-option " "bytes\n instead of the real GRE/mobile header when " "ip_hl > 5.\n"); return 0; /* bug demonstrated */ } printf("VERDICT: no misparse (overlay reads match ip_hl-aware reads)\n"); return 1; } |