โฌข DragonFlyBSD Kernel Audit
DF-4159 / livelock_harness.c
โ† back to finding โ†“ download raw
/*
 * DF-4159 logic-model harness โ€” RX livelock in sys/dev/netif/xe/if_xe.c xe_intr()
 *
 * This is NOT the kernel and NOT a guest run: the Xircom PCMCIA hardware does
 * not exist in the QEMU guest, so this harness models the card's register
 * interface with the semantics documented in sys/dev/netif/xe/if_xereg.h and
 * cross-validated against Linux's xirc2ps_cs.c driver for the same silicon:
 *
 *   - XE_ESR_FULL_PACKET_RX (ESR bit 0) stays asserted while an un-released
 *     (not skipped) full packet sits in the card RX buffer.
 *   - The ONLY way to release/skip a packet is XE_OUTW(XE_DO, 0x8000)
 *     (XE_DO_SKIP_RX_PACKET).  Linux issues this unconditionally at the end
 *     of every iteration โ€” including drops โ€” with the comment
 *     "clear the received/dropped/error packet".
 *
 * The loop below is copied verbatim (control-flow identical) from
 * if_xe.c:742-844.  MODE selects which drop paths release the packet:
 *   MODE 0 = DragonFly code as written  (continue WITHOUT XE_DO write)
 *   MODE 1 = proposed fix               (XE_DO write before each continue)
 * FAIL_MGET / FAIL_MCL / LEN_ZERO select the error path taken.
 * The loop is capped at 200000 iterations; hitting the cap means the loop
 * would spin forever in the kernel.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* ---- mocked card register file -------------------------------------- */
static unsigned char reg_esr, reg_rsr;
static unsigned short reg_rbc, reg_do;
static unsigned long  do_skip_writes;      /* count of XE_DO=0x8000 writes */
static unsigned long  esr_reads;

#define XE_ESR_FULL_PACKET_RX 0x01
#define XE_RSR_RX_OK          0x20         /* per if_xereg.h */
#define XE_DO_SKIP_RX_PACKET  0x8000
#define ETHER_CRC_LEN         4

static unsigned char XE_INB_ESR(void) {
	esr_reads++;
	return reg_esr;
}
static unsigned short XE_INW_RBC(void) { return reg_rbc; }
static void XE_OUTW_DO(unsigned short v) {
	if (v == XE_DO_SKIP_RX_PACKET) {
		do_skip_writes++;
		/* packet released: next ESR read shows buffer empty */
		reg_esr &= ~XE_ESR_FULL_PACKET_RX;
	}
	reg_do = v;
}

/* one received frame queued in the card buffer */
static void card_deliver_frame(unsigned short rbc)
{
	reg_rbc = rbc;
	reg_rsr = XE_RSR_RX_OK;
	reg_esr |= XE_ESR_FULL_PACKET_RX;
}

int main(int argc, char **argv)
{
	int mode = atoi(argv[1]);      /* 0 = code as written, 1 = fixed */
	int fail_mget = atoi(argv[2]);
	int fail_mcl = atoi(argv[3]);
	int len_zero = atoi(argv[4]);
	unsigned long iter = 0, iqdrops = 0;
	const unsigned long CAP = 200000;

	card_deliver_frame(len_zero ? ETHER_CRC_LEN : 1518);

	/* ==== loop copied from if_xe.c:742-844 (drop paths parameterized) == */
	while ((reg_esr = XE_INB_ESR()) & XE_ESR_FULL_PACKET_RX) {
		unsigned char rsr = reg_rsr;
		if (++iter > CAP) {
			printf("RESULT: LIVELOCK โ€” loop exceeded %lu "
			       "iterations; packet NEVER released "
			       "(XE_DO skip writes: %lu)\n",
			       CAP, do_skip_writes);
			return 2;
		}
		if (rsr & XE_RSR_RX_OK) {
			unsigned short len = XE_INW_RBC() - ETHER_CRC_LEN;

			if (len == 0) {
				iqdrops++;
				if (mode) XE_OUTW_DO(XE_DO_SKIP_RX_PACKET);
				continue;              /* if_xe.c:758 */
			}

			/* MGETHDR */
			if (fail_mget) {
				iqdrops++;
				if (mode) XE_OUTW_DO(XE_DO_SKIP_RX_PACKET);
				continue;              /* if_xe.c:772 */
			}
			/* MCLGET for len+3 > MHLEN */
			if (len + 3 > 224 && fail_mcl) {
				iqdrops++;
				if (mode) XE_OUTW_DO(XE_DO_SKIP_RX_PACKET);
				continue;              /* if_xe.c:779 */
			}
			/* ... packet copy + if_input elided: irrelevant to
			 * the livelock (never reached on drop paths) ... */
		}
		/* Skip to next packet, if there is one */
		XE_OUTW_DO(XE_DO_SKIP_RX_PACKET);   /* if_xe.c:843 */
	}
	/* ==================================================================== */

	printf("RESULT: loop terminated normally after %lu iterations "
	       "(drops: %lu, XE_DO skip writes: %lu, ESR reads: %lu)\n",
	       iter, iqdrops, do_skip_writes, esr_reads);
	return (iqdrops && !do_skip_writes) ? 2 : 0;
}