โฌข DragonFlyBSD Kernel Audit
DF-4217 / stoploop.c
โ† back to finding โ†“ download raw
/*
 * DF-4217 stoploop.c โ€” termination model of the two idle-wait loops in
 * ae_stop_rxmac()/ae_stop_txmac() (sys/dev/netif/ae/if_ae.c:288 and :324).
 *
 * Both loops are written:
 *
 *     for (i = 0; i < AE_IDLE_TIMEOUT; i--) {          <-- i--, not i++
 *             val = AE_READ_4(sc, AE_IDLE_REG);
 *             if ((val & (AE_IDLE_RXMAC | AE_IDLE_DMAWRITE)) == 0)
 *                     break;
 *             DELAY(100);
 *     }
 *     if (i == AE_IDLE_TIMEOUT) {        <-- unreachable: i never rises
 *             if_printf(... "timed out while stopping ... MAC.\n");
 *     }
 *
 * AE_IDLE_TIMEOUT is 100 (if_aevar.h:41), i.e. an intended 10 ms wait.
 * But i starts at 0 and is only ever DECREMENTED, so (i < 100) is true
 * on every iteration forever: unless the device happens to report idle,
 * the loop NEVER terminates โ€” it busy-waits in DELAY(100) holding
 * ifp->if_serializer โ€” and the post-loop timeout branch can never fire
 * because i can never equal 100.  (Compare the correct sibling loops:
 * ae_stop() itself :255 and ae_reset() :1510 both use i++.)
 *
 * Trigger reality: AE_READ_4 on a surprise-removed / halted / errored
 * device returns 0xFFFFFFFF, so the IDLE bits stay set and the loop is
 * fed forever.  Reachable from every ae_stop()/ae_stop_{r,t}xmac()
 * caller: ifconfig down/up, watchdog, init-from-intr, suspend, detach,
 * miibus statchg.
 *
 * This model runs the exact guard/increment/exit shape of if_ae.c:288
 * with a register stub that never idles, self-capped at 10,000,000
 * iterations so the model itself terminates (the kernel loop has no
 * such cap).  The patched (i++) variant is then run against the same
 * stub to show it terminates at exactly AE_IDLE_TIMEOUT reads.
 */
#include <stdio.h>

#define	AE_IDLE_TIMEOUT	100
#define	IDLE_BITS	0x00000030	/* AE_IDLE_RXMAC | AE_IDLE_DMAWRITE */

static unsigned long nreads;

static unsigned int
dead_device_idle_reg(void)
{
	nreads++;			/* AE_READ_4: surprise-removed device */
	return (0xFFFFFFFFu);		/* -> IDLE bits always set */
}

int
main(void)
{
	int i;
	unsigned long iters;
	int timeout_branch_taken;

	printf("DF-4217 model: ae_stop_rxmac/ae_stop_txmac idle-wait "
	    "termination\n(sys/dev/netif/ae/if_ae.c:288 and :324, "
	    "AE_IDLE_TIMEOUT=%d)\n\n", AE_IDLE_TIMEOUT);

	/* [1] as written: for (i = 0; i < AE_IDLE_TIMEOUT; i--) */
	puts("[1] as-written (i--) vs a device that never reports idle:");
	iters = 0;
	for (i = 0; i < AE_IDLE_TIMEOUT; i--) {		/* if_ae.c:288/:324 */
		unsigned int val = dead_device_idle_reg();
		iters++;
		if ((val & IDLE_BITS) == 0)
			break;				/* device idled */
		if (iters >= 10000000UL) {
			puts("    MODEL SELF-CAP HIT (10,000,000 iterations) "
			    "-- the kernel loop has NO cap;");
			puts("    guard (i < 100) is permanently true because "
			    "i only decrements from 0.");
			break;
		}
	}
	timeout_branch_taken = (i == AE_IDLE_TIMEOUT);	/* the post-loop test */
	printf("    iterations executed (model-capped): %lu\n", iters);
	printf("    post-loop timeout branch `i == AE_IDLE_TIMEOUT` taken: %s"
	    " (i=%d)\n",
	    timeout_branch_taken ? "YES" : "NO -- unreachable by construction",
	    i);

	/* [2] fixed: for (i = 0; i < AE_IDLE_TIMEOUT; i++) */
	nreads = 0;
	for (i = 0; i < AE_IDLE_TIMEOUT; i++) {
		unsigned int val = dead_device_idle_reg();
		if ((val & IDLE_BITS) == 0)
			break;
	}
	printf("[2] fixed (i++): same dead device -> loop exits after exactly "
	    "%lu reads\n    (10 ms), then the \"timed out while stopping "
	    "Rx/Tx MAC.\" message fires. Bounded either way.\n\n", nreads);

	puts("TRIGGER_RC=0 (model OK; hardware-gated on this guest: "
	    "no Attansic L2 NIC)");
	return (0);
}