⬢ DragonFlyBSD Kernel Audit
DF-3990 / trigger.c
← back to finding ↓ download raw
/*
 * DF-3990 trigger.c — model of the two unbounded device-fed index walks in
 * ale(4) (DF-3761 alc / DF-3778 jme / DF-3934 age family, ale instance).
 *
 * (1) ale_rxeof (sys/dev/netif/ale/if_ale.c:2153-2254) has NO work bound:
 *     the loop runs while (cons < prod) where prod is the controller-DMA'd
 *     CMB word (:2176) and is refreshed from the new page's CMB on every
 *     page flip (ale_rx_update_page :2091).  There is no frame budget --
 *     ALE_PROC_MIN/ALE_PROC_MAX/ALE_PROC_DEFAULT are defined in
 *     if_alevar.h:118-121 and NEVER referenced anywhere (dead knobs, same
 *     as age DF-3934).  A line-rate small-frame flood (or faulty device
 *     keeping the CMB ahead) keeps prod > cons forever, so the interrupt
 *     handler spins in m_devget+if_input holding ifp->if_serializer:
 *     NIC + CPU livelock, interface wedge.
 *
 * (2) ale_txeof (if_ale.c:2009-2058): prod = CSR_READ_2(ALE_TPD_CONS_IDX)
 *     (:2026) is a 16-bit register value NOT masked to ALE_TX_RING_CNT
 *     (256).  The walk `for (; cons != prod; ALE_DESC_INC(cons,256))`
 *     frees tx_m on every descriptor between cons and prod, bounded only
 *     by ale_tx_cnt reaching 0 — a device-fed prod ahead of true
 *     completion over-reclaims: mbufs freed (and dmamaps unloaded) while
 *     the descriptors still own them.
 *
 * This model simulates both walks with a device that keeps prod ahead and
 * prints iteration counts / reclaim extents.
 */
#include <stdio.h>
#include <stdint.h>

#define TX_RING_CNT	256
#define TX_CNT_START	40	/* queued descriptors at interrupt entry */

int main(void)
{
	int i;

	printf("DF-3990 model: unbounded ale_rxeof walk + unmasked ale_txeof "
	    "producer\n\n");

	/* (1) rxeof: device keeps CMB prod at cons+1 forever */
	printf("[1] ale_rxeof: loop guard is only (cons < prod); prod is\n"
	    "    device-fed and re-read on page flip. Simulating a device\n"
	    "    that keeps prod == cons+1 for 1,000,000 'delivered' slots:\n");
	uint64_t iters = 0;
	uint32_t cons = 0, prod;
	for (i = 0; i < 1000000; i++) {
		prod = cons + 1;	/* malicious/flooded CMB */
		while (cons < prod) {	/* if_ale.c:2178 */
			cons += 32;	/* one frame consumed */
			iters++;	/* each did m_devget + if_input */
		}
		if (cons >= 196608)
			cons = 0;	/* page flip re-reads CMB (:2091) */
	}
	printf("    iterations completed=%llu, budget used by driver=0 "
	    "(ALE_PROC_* dead knobs) -> with a real device keeping prod "
	    "ahead this never ends (serializer held)\n\n",
	    (unsigned long long)iters);

	/* (2) txeof: unmasked 16-bit producer vs 256-ring */
	printf("[2] ale_txeof: prod=CSR_READ_2(0x1804) unmasked (0..65535), "
	    "ring=%d\n", TX_RING_CNT);
	uint32_t cons2 = 100, prod_dev = 1000;	/* device-fed garbage */
	int tx_cnt = TX_CNT_START, freed = 0, prog = 0;
	for (; cons2 != prod_dev; prog++, cons2 = (cons2 + 1) % TX_RING_CNT) {
		if (tx_cnt <= 0)
			break;	/* :2034 only bound */
		tx_cnt--;
		freed++;	/* every EOP desc's m_freem+dmamap_unload */
	}
	printf("    prod=%u (not congruent in ring) -> cons never equals it; "
	    "walk bounded ONLY by tx_cnt: freed ALL %d queued frames' "
	    "mbufs+dmamaps regardless of real completion -> OVER-RECLAIM "
	    "(premature m_freem of descriptors the NIC still owns)\n",
	    prod_dev, freed);
	printf("\nTRIGGER_RC=0 (model OK; hardware-gated on this guest: "
	    "no Atheros NIC)\n");
	return (0);
}