DF-3447 / bpffailopen.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 | /* * DF-3447 -- BPF_LD|BPF_H|BPF_ABS fail-open on the kernel mbuf path. * * sys/net/bpf_filter.c:238-247 (_KERNEL, buflen==0 i.e. mbuf path): * * case BPF_LD|BPF_H|BPF_ABS: * k = pc->k; * if (k > buflen || sizeof(int16_t) > buflen - k) { * #ifdef _KERNEL * int merr; * if (buflen != 0) * return 0; * A = m_xhalf((struct mbuf *)p, k, &merr); * continue; <-- merr NEVER checked * #else * return 0; * #endif * } * * m_xhalf() sets *err=1 and returns 0 when the halfword is out of the * mbuf chain. The BPF_W twin two cases above checks merr and returns 0 * (reject). The H variant ignores merr and keeps running with A=0 -- * fail-open: the filter program continues instead of rejecting. * * Demonstration on the live kernel: two filters on /dev/bpf0@lo0. * * filtH: LD|H|ABS k=1000 (out of range for a ~76-byte packet) * JEQ #0 -> RET 0xffffffff (accept) ; RET 0 (reject) * filtW: LD|W|ABS k=1000 (same offset, word flavor) * JEQ #0 -> RET 0xffffffff ; RET 0 * * Userland semantics (the #else branch): both return 0 -> packet * rejected. Kernel: filtW rejects (merr honored); filtH ACCEPTS the * packet because the load silently yields A=0 and the program runs on. * Captured-by-filtH but not by-filtW on the same traffic = fail-open * proven live. * * root only (/dev/bpf). Build: cc -O -o bpffailopen bpffailopen.c */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/fcntl.h> #include <net/bpf.h> #include <net/if.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> /* * filtH: (000) LDH ABS 1000 (001) JEQ #0 jt 2 jf 3 * (002) RET 0xffffffff (003) RET 0 */ static struct bpf_insn insns_h[] = { { 0x28, 0, 0, 1000 }, { 0x15, 0, 1, 0 }, { 0x06, 0, 0, 0xffffffff }, { 0x06, 0, 0, 0 }, }; /* filtW: LDW ABS 1000, same tail */ static struct bpf_insn insns_w[] = { { 0x20, 0, 0, 1000 }, { 0x15, 0, 1, 0 }, { 0x06, 0, 0, 0xffffffff }, { 0x06, 0, 0, 0 }, }; static int open_free_bpf(void) { char dev[16]; for (int i = 0; i < 16; i++) { snprintf(dev, sizeof(dev), "/dev/bpf%d", i); int fd = open(dev, O_RDWR); if (fd >= 0) return (fd); if (errno != EBUSY) { perror(dev); exit(1); } } fprintf(stderr, "no free /dev/bpf*\n"); exit(1); } static int setup_bpf(const char *ifname, struct bpf_insn *insns, size_t nins) { struct ifreq ifr; u_int immediate = 1; struct bpf_program prog; int fd = open_free_bpf(); memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, (char *)ifname, sizeof(ifr.ifr_name)); if (ioctl(fd, BIOCSETIF, &ifr) < 0) { perror("BIOCSETIF"); exit(1); } if (ioctl(fd, BIOCIMMEDIATE, &immediate) < 0) { perror("BIOCIMMEDIATE"); exit(1); } prog.bf_len = nins; prog.bf_insns = insns; if (ioctl(fd, BIOCSETF, &prog) < 0) { perror("BIOCSETF"); exit(1); } return (fd); } int main(void) { static char bufH[65536], bufW[65536]; /* read() must pass EXACTLY bd_bufsize */ int fdH, fdW, nH, nW, traffic = 0; if (geteuid() != 0) { fprintf(stderr, "root only\n"); return (1); } u_int blenH = 0, blenW = 0; fdH = setup_bpf("vtnet0", insns_h, sizeof(insns_h)/sizeof(insns_h[0])); fdW = setup_bpf("vtnet0", insns_w, sizeof(insns_w)/sizeof(insns_w[0])); ioctl(fdH, BIOCGBLEN, &blenH); ioctl(fdW, BIOCGBLEN, &blenW); { struct timeval rt = { 2, 0 }; ioctl(fdH, BIOCSRTIMEOUT, &rt); ioctl(fdW, BIOCSRTIMEOUT, &rt); } printf("filters loaded on vtnet0 (bpf_mtap mbuf path): " "H(k=1000 out-of-range halfword) and W(k=1000 word)\n"); printf("generating 5 small vtnet0 packets (ping 10.0.2.2)...\n"); fflush(stdout); for (int i = 0; i < 5; i++) { system("ping -c 1 -s 16 -t 2 10.0.2.2 >/dev/null 2>&1"); usleep(200000); nH = read(fdH, bufH, blenH); nW = read(fdW, bufW, blenW); printf("packet %d: captured-by-H=%d captured-by-W=%d\n", i + 1, nH > 0, nW > 0); if (nH > 0) traffic++; } if (traffic > 0) { printf("\nVERDICT: REPRODUCED -- out-of-range BPF_H load kept the " "filter running (fail-open, packets accepted) while the " "identical BPF_W filter correctly rejected them\n"); return (0); } printf("\nVERDICT: not reproduced (H filter rejected everything too)\n"); return (1); } |