โฌข DragonFlyBSD Kernel Audit
DF-3757 / txput_setup_guard_model.c
โ† back to finding โ†“ download raw
/*
 * DF-3757 โ€” userspace logic model of the ring-full guard defect in
 * sys/dev/netif/de/if_de.c:tulip_txput_setup() (lines 3631-3636).
 *
 * Kernel code:
 *     if (ri->ri_free < 2)
 *         tulip_tx_intr(sc);                              // 3631: reclaim attempt
 *     if ((sc->tulip_flags & TULIP_DOINGSETUP) || ri->ri_free == 1) {  // 3633
 *         sc->tulip_flags |= TULIP_WANTTXSTART;
 *         return;                                         // 3634-3635: defer
 *     }
 *     ...
 *     ri->ri_free--;                                      // 3644
 *     nextout = ri->ri_nextout->di_desc;                  // 3645: program desc
 *     ...
 *     nextout->d_status = TULIP_DSTS_OWNER;               // 3675
 *
 * The deferral guard tests `ri->ri_free == 1` but NOT `ri->ri_free == 0`.
 * When the TX ring is completely full of chip-owned descriptors (128/128
 * in flight, ri_free == 0 and tulip_tx_intr() reclaims nothing because
 * every descriptor still has TULIP_DSTS_OWNER set โ€” if_de.c:3169), the
 * function falls through and:
 *   1. ri_free underflows to -1 (3644)  โ€” permanent ring-accounting drift;
 *   2. the setup frame is written over a descriptor the chip owns (3645-3675),
 *      OR-ing TxSETUPPKT|TxLASTSEG into an in-flight packet descriptor;
 *   3. when the chip later completes that descriptor, tulip_tx_intr() takes
 *      the SETUPPKT branch (3176) and never frees the packet's mbuf
 *      (free happens only in the non-setup branch, 3197-3202) -> mbuf leak;
 *   4. the next tulip_txput() hits KASSERT(di_mbuf == NULL) (3544) on
 *      INVARIANTS kernels -> panic.
 *
 * Trigger path (no privilege needed): unprivileged IP_ADD_MEMBERSHIP ->
 * in_addmulti() -> if_addmulti() -> if_addmulti_serialized() calls
 * ifp->if_ioctl(SIOCADDMULTI) (sys/net/if.c:2739) under the serializer ->
 * tulip_ifioctl:3719 tulip_addr_filter() sets TULIP_WANTSETUP ->
 * tulip_init:2960-2961 -> tulip_txput_setup().  Requires the TX ring to be
 * 100% chip-owned at that instant (saturation) on a real de(4) NIC.
 *
 * Build:  cc -O2 -Wall -o txput_setup_guard_model txput_setup_guard_model.c
 * Run:    ./txput_setup_guard_model
 */
#include <stdio.h>
#include <stdint.h>

#define	TULIP_TXDESCS		128
#define	TULIP_DSTS_OWNER	0x80000000u
#define	TULIP_DFLAG_TxFIRSTSEG	0x0800
#define	TULIP_DFLAG_TxLASTSEG	0x1000
#define	TULIP_DFLAG_TxSETUPPKT	0x0400
#define	TULIP_DFLAG_TxWANTINTR	0x2000
#define	TULIP_WANTSETUP		0x00000001u
#define	TULIP_DOINGSETUP	0x00000008u
#define	TULIP_WANTTXSTART	0x00004000u

struct desc { uint32_t d_status, d_addr1, d_length1; uint16_t d_flag; };
struct ringinfo {
	int ri_free, ri_max;
	int ri_nextout_idx, ri_nextin_idx;
	struct desc descs[TULIP_TXDESCS];
};

/* mirror of tulip_tx_intr() reclaim (if_de.c:3165-3172): only non-OWNER
 * descriptors are reclaimed; with the ring 100% chip-owned it returns 0. */
static int
tx_intr_reclaim(struct ringinfo *ri)
{
	int descs = 0;
	while (ri->ri_free < ri->ri_max) {
		if (ri->descs[ri->ri_nextin_idx].d_status & TULIP_DSTS_OWNER)
			break;			/* chip still owns it */
		ri->ri_free++;
		descs++;
		ri->ri_nextin_idx = (ri->ri_nextin_idx + 1) % TULIP_TXDESCS;
	}
	return (descs);
}

int
main(void)
{
	struct ringinfo ri;
	uint32_t flags = TULIP_WANTSETUP;
	int i, reclaimed;

	/* ring 100% full of chip-owned (in-flight) descriptors */
	ri.ri_max = TULIP_TXDESCS;
	ri.ri_free = 0;
	ri.ri_nextout_idx = ri.ri_nextin_idx = 0;
	for (i = 0; i < TULIP_TXDESCS; i++) {
		ri.descs[i].d_status = TULIP_DSTS_OWNER;	/* chip owns all 128 */
		ri.descs[i].d_flag = 0;
	}

	/* ---- 1:1 mirror of tulip_txput_setup() if_de.c:3631-3675 ---- */
	if (ri.ri_free < 2)
		reclaimed = tx_intr_reclaim(&ri);
	else
		reclaimed = 0;
	printf("tulip_tx_intr reclaim attempt: %d descriptors reclaimed "
	       "(all %d still chip-owned)\n", reclaimed, TULIP_TXDESCS);

	if ((flags & TULIP_DOINGSETUP) || ri.ri_free == 1) {	/* 3633 โ€” the guard */
		flags |= TULIP_WANTTXSTART;
		printf("deferred (ri_free==1 path)\n");
		return (1);					/* not this scenario */
	}
	printf("guard `ri->ri_free == 1' did NOT catch ri_free == %d -> falls through\n",
	       ri.ri_free);

	ri.ri_free--;						/* 3644 */
	printf("ri->ri_free = %d   (UNDERFLOW: accounting now negative)\n", ri.ri_free);

	{
		struct desc *nextout = &ri.descs[ri.ri_nextout_idx];
		uint32_t chip_had_owner = nextout->d_status & TULIP_DSTS_OWNER;
		nextout->d_flag &= 0;				/* 3646: clobber in-flight flags */
		nextout->d_flag |= TULIP_DFLAG_TxFIRSTSEG | TULIP_DFLAG_TxLASTSEG
			| TULIP_DFLAG_TxSETUPPKT | TULIP_DFLAG_TxWANTINTR; /* 3647-3648 */
		nextout->d_length1 = 192;			/* 3656 */
		nextout->d_status = TULIP_DSTS_OWNER;		/* 3675 */
		printf("setup frame programmed over descriptor %d "
		       "(chip-owned before write: %s)\n",
		       ri.ri_nextout_idx, chip_had_owner ? "YES โ€” ownership violation" : "no");
		printf("in-flight packet descriptor now tagged TxSETUPPKT|TxLASTSEG: "
		       "tulip_tx_intr SETUPPKT branch (if_de.c:3176) will skip "
		       "m_freem() -> mbuf+cluster leak\n");
	}

	/* downstream: next tulip_txput() bookkeeping starts from ri_free = -1 */
	printf("next tulip_txput(): free = ri->ri_free = %d (negative), "
	       "`if (free == 0)' reclaim at if_de.c:3445 SKIPPED\n", ri.ri_free);
	printf("KASSERT(eop->di_mbuf == NULL) at if_de.c:3544 fires on "
	       "INVARIANTS kernels once the clobbered desc is re-used\n");

	printf("\nSUCCESS-CRITERION: ri_free underflows to -1 and a chip-owned "
	       "descriptor is reprogrammed -> logic model of if_de.c:3633 defect CONFIRMED\n");
	return (0);
}