DF-0701 / ipfw3_sync_deadpath.c
/* * DF-0700 / DF-0701 / DF-0702 / DF-0703 โ dead-path demonstration. * * All four findings live in sys/net/ipfw3_basic/ip_fw3_sync.c (the ipfw3 * state-synchronization sub-component). They are reached, in principle, via the * IP_FW_SYNC_* sockopt cases in ip_fw3_ctl() (sys/net/ipfw3/ip_fw3.c). This * program empirically demonstrates the path is DEAD on a default kernel: * * userspace setsockopt(IPPROTO_IP, IP_FW_X, {opcode=IP_FW_SYNC_CENTRE_CONF,...}) * -> raw_ip.c:386 ip_fw3_sockopt(sopt) [base-kernel STUB] * -> ip_fw3_glue.c:57 if (IPFW3_LOADED) ... else return ENOPROTOOPT; * * Without `kldload ipfw3` (root), every IP_FW_X sockopt returns ENOPROTOOPT โ * the SYNC_* cases are never even reached. And EVEN WITH the module loaded, the * SYNC_* dispatch in ip_fw3_ctl() is gated on: * * if (ip_fw3_ctl_sync_ptr != NULL) // ip_fw3.c:1125 * error = ip_fw3_ctl_sync_ptr(sopt); * * but `ip_fw3_ctl_sync_ptr` (ip_fw3.c:133) is initialized to NULL and is NEVER * assigned anywhere in the entire source tree (grep-verified). So even * post-kldload, the SYNC_* sockopts are silent no-ops (error stays 0). The * buggy functions โ ip_fw3_ctl_sync_centre_conf/_centre_start/_centre_test/ * _edge_socket_handler/ip_fw3_sync_send_state โ are unreachable from userspace. * * Run as unprivileged maxx: prints the ENOPROTOOPT (or, on a hypothetical * system where the ptr was wired up, the EINVAL from the fix's bounds check). * * Build: cc -o ipfw3_sync_deadpath ipfw3_sync_deadpath.c * Run : ./ipfw3_sync_deadpath */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <unistd.h> #ifndef IP_FW_X #define IP_FW_X 49 /* sys/netinet/in.h: ipfw3 firewall sockopt */ #endif /* ip_fw_x_header + ipfw3_ioc_sync_centre (count=9999, way over MAX_EDGES=10) */ struct ip_fw_x_header { uint16_t opcode; uint16_t pad; }; struct ipfw3_sync_edge { uint32_t addr; uint16_t port; }; struct ipfw3_ioc_sync_centre { int count; struct ipfw3_sync_edge edges[0]; }; #define IP_FW_SYNC_CENTRE_CONF 89 int main(void) { int s, rc; /* opcode=IP_FW_SYNC_CENTRE_CONF, followed by an out-of-range count */ struct { struct ip_fw_x_header h; struct ipfw3_ioc_sync_centre c; struct ipfw3_sync_edge e; /* one dummy edge */ } __attribute__((packed)) msg; s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { if (errno == EPERM || errno == EACCES) { /* SOCK_RAW needs root on DragonFly; fall back to SOCK_DGRAM */ s = socket(AF_INET, SOCK_DGRAM, 0); } if (s < 0) { perror("socket"); return 1; } } memset(&msg, 0, sizeof(msg)); msg.h.opcode = IP_FW_SYNC_CENTRE_CONF; msg.c.count = 9999; /* would be a clear BSS OOB write if reachable */ rc = setsockopt(s, IPPROTO_IP, IP_FW_X, &msg, sizeof(msg)); printf("setsockopt(IPPROTO_IP, IP_FW_X, {SYNC_CENTRE_CONF, count=9999}) = %d\n", rc); if (rc < 0) printf(" errno=%d (%s)\n", errno, strerror(errno)); else printf(" returned SUCCESS (silent no-op: ip_fw3_ctl_sync_ptr is NULL)\n"); printf("\nInterpretation:\n"); if (rc < 0 && errno == ENOPROTOOPT) printf(" ENOPROTOOPT => ipfw3 module not loaded; the SYNC_* cases in\n" " ip_fw3_ctl() are never reached. (Default kernel state.)\n"); else if (rc < 0 && (errno == EPERM || errno == EACCES)) printf(" EPERM/EACCES => raw-sockopt needs privilege; the underlying\n" " reachability argument is unchanged (see VERDICT.md).\n"); else if (rc == 0) printf(" SUCCESS-no-op => ipfw3 loaded BUT ip_fw3_ctl_sync_ptr (ip_fw3.c:133)\n" " is never assigned, so the SYNC_* dispatch (ip_fw3.c:1125-1126) is\n" " skipped and the buggy centre_conf() is never called.\n"); /* The 4 bugs are CONFIRMED at source level but UNREACHABLE on this kernel. See VERDICT.md for the path:line trace and fix.diff for the hardening. */ close(s); return 0; } |