⬢ DragonFlyBSD Kernel Audit
DF-3761 / trigger.c
← back to finding ↓ download raw
/*
 * DF-3761 trigger - alc(4) alc_rxintr() ignores alc_process_limit
 * ==============================================================
 *
 * HARDWARE PRECONDITION: an Atheros AR81xx NIC receiving a sustained
 * line-rate packet flood.  The audit guest has no such device (env.txt:
 * NO_ATHEROS_PCI, ifconfig -l = "vtnet0 lo0"), so this file documents
 * the trigger; there is nothing meaningful to execute here beyond the
 * presence probe in run.sh.
 *
 * DragonFly alc_rxintr() (sys/dev/netif/alc/if_alc.c:3222-3289):
 *
 *     for (prog = 0; (ifp->if_flags & IFF_RUNNING) != 0;) {
 *             rrd = &sc->alc_rdata.alc_rr_ring[rr_cons];
 *             status = le32toh(rrd->status);
 *             if ((status & RRD_VALID) == 0)
 *                     break;
 *             ...
 *             alc_rxeof(sc, rrd);          // allocates a fresh cluster
 *                                          // per Rx buffer (alc_newbuf
 *                                          // :3187 m_getcl + dmamap load)
 *     }
 *
 * has NO work bound.  The driver *does* expose a bound - the
 * process_limit knob (sysctl hw.alcN.process_limit, if_alc.c:1696-1713,
 * ALC_PROC_DEFAULT 64, if_alcvar.h:71-73) - but nothing in the driver
 * ever reads it in the loop: it is a dead knob.
 *
 * FreeBSD bounds the identical loop (sys/dev/alc/if_alc.c,
 * alc_rxintr(sc, count): "if (count-- <= 0) break;"), and DragonFly's
 * sibling driver msk(4) bounds its Rx walk too
 * (sys/dev/netif/msk/if_msk.c:3225 "if (rxprog > sc->msk_process_limit)").
 *
 * Attack shape (remote, unauthenticated, default config):
 *   flood alc0 at line rate (1.488 Mpps of 64-byte frames at 1 GbE);
 *   per frame alc_rxintr performs an m_getcl cluster allocation +
 *   bus_dmamap_load + RRS processing + if_input enqueue, which is
 *   slower than the arrival rate, so the return ring is perpetually
 *   non-empty and the loop never terminates.  alc_intr disabled the
 *   NIC interrupt at entry (if_alc.c:3082, INTR_DIS_INT) and only
 *   re-enables it after the loop (:3110), and the whole loop runs with
 *   the interface serializer held:
 *
 *     - the NIC's interrupt thread pins one CPU at 100% forever;
 *     - every other driver entry point that needs the serializer
 *       (ifconfig, any ioctl, alc_tick) blocks indefinitely;
 *     - callouts on that CPU starve (the interrupt thread never yields).
 *
 * Fix: bound the loop with the existing knob (fix.diff).
 */
int DF_3761_trigger_is_a_documented_remote_flood;	/* nothing to run */