โฌข DragonFlyBSD Kernel Audit
DF-5530 / bpfdetach_null.c
โ† back to finding โ†“ download raw
/*
 * DF-5530 โ€” deterministic sink proof for the bpfdetach() NULL-deref.
 *
 * sys/net/bpf.c:1567-1574 โ€” the bpf_if search loop can legitimately exit
 * with bp == NULL (interface never bpf-attach'ed, or already de-attached),
 * and the "Interface wasn't attached" guard then dereferences bp->bif_ifp
 * on the NULL pointer:
 *
 *   for (bp = bpf_iflist; bp != NULL; bp = bp->bif_next) {
 *           if (ifp == bp->bif_ifp)
 *                   break;
 *           bp_prev = bp;
 *   }
 *   if (bp->bif_ifp == NULL) {      <-- must be (bp == NULL)
 *
 * Reachable from sys/bus/u4b/usb_pf.c: usbpf_clone_destroy() (usb_pf.c:192)
 * calls bpfdetach(ifp) on an ifnet that usbpf_clone_create() published at
 * usb_pf.c:157 (ubus->ifp = if_alloc(IFT_USB)) *before* bpfattach() at
 * usb_pf.c:177.  usbpf_detach() (usb_pf.c:209-210, called from controller
 * detach, usb_controller.c:240) and usbpf_uninit() (usb_pf.c:114-118) call
 * the destroy path with no lock synchronizing them against the create path,
 * so a destroy that wins the 157->177 window hands bpfdetach() an ifnet
 * that is not in bpf_iflist -> bp == NULL -> fatal page fault.
 *
 * This module reproduces exactly that state: a freshly allocated IFT_USB
 * ifnet, named like the clone would name it, that has not been
 * bpf-attach'ed yet โ€” then invokes the production bpfdetach() on it.
 *
 * Build: see build.sh (KLD, needs /usr/src/sys).
 * Run:   kldload ./bpfdetach_null.ko        (as root)
 * Expected on a vulnerable kernel:
 *   Fatal trap 12: page fault while in kernel mode, faultvaddr == small
 *   (offsetof(struct bpf_if, bif_ifp)), RIP in bpfdetach (bpf.c:1574).
 * Expected on a patched kernel (fix: `if (bp == NULL)`):
 *   kprintf "bpfdetach: usbus0 was not attached", no panic, module loads.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_types.h>
#include <net/bpf.h>

static int
bpfdetach_null_load(struct module *m, int what, void *arg)
{
	struct ifnet *ifp;

	switch (what) {
	case MOD_LOAD:
		/* state == usbpf_clone_create() between usb_pf.c:157 and :177 */
		ifp = if_alloc(IFT_USB);
		if (ifp == NULL) {
			kprintf("DF5530: if_alloc failed\n");
			return (ENOSPC);
		}
		ksnprintf(ifp->if_xname, sizeof(ifp->if_xname), "usbus0");

		kprintf("DF5530: calling bpfdetach() on not-bpf-attached %s "
		    "(simulating usbpf_clone_destroy() winning the "
		    "usb_pf.c:157->177 window)\n", ifp->if_xname);

		bpfdetach(ifp);		/* <- panics at bpf.c:1574 */

		kprintf("DF5530: bpfdetach() returned (not reached on "
		    "vulnerable kernel)\n");
		if_free(ifp);
		break;
	case MOD_UNLOAD:
		kprintf("DF5530: unloaded\n");
		break;
	default:
		break;
	}
	return (0);
}

static moduledata_t bpfdetach_null_mod = {
	"bpfdetach_null",
	bpfdetach_null_load,
	NULL
};

DECLARE_MODULE(bpfdetach_null, bpfdetach_null_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_VERSION(bpfdetach_null, 1);