⬢ DragonFlyBSD Kernel Audit
DF-3447 / bpffailopen.c
← back to finding ↓ download raw
/*
 * 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);
}