/*
 * DF-4219 trigger.c — model of ae_rx_intr()'s unbounded descriptor walk
 * (sys/dev/netif/ae/if_ae.c:643-668), the DF-3990 (ale) family instance
 * for ae(4).
 *
 * The walk is:
 *
 *     for (;;) {                                        if_ae.c:643
 *             rxd = sc->rxd_base + sc->rxd_cur;         :644
 *             flags = le16toh(rxd->flags);              :646 (device DMA)
 *             if ((flags & AE_RXD_UPDATE) == 0)         :647
 *                     break;                            -- the ONLY exit
 *             rxd->flags = htole16(flags & ~AE_RXD_UPDATE);  :649 driver clears
 *             ae_update_stats_rx(flags, &sc->stats);    :652
 *             sc->rxd_cur = (sc->rxd_cur + 1) % AE_RXD_COUNT_DEFAULT; :657
 *             if ((flags & AE_RXD_SUCCESS) == 0) { ierrors++; continue; } :658
 *             error = ae_rxeof(sc, rxd);                :663 (m_devget + if_input)
 *     }
 *     AE_WRITE_2(sc, AE_MB_RXD_IDX_REG, sc->rxd_cur);   :671 only AFTER exit
 *
 * AE_RXD_UPDATE (0x8000, if_aereg.h:384) is a controller-written bit:
 * the loop terminates only when the driver finds a descriptor the
 * device has NOT marked.  There is no work budget anywhere in ae(4)
 * (unlike ale(4), which at least defines dead ALE_PROC_* knobs) — the
 * "budget" is implicitly the 64-descriptor ring, but the device re-marks
 * descriptors as fast as the driver clears them.  A controller that
 * keeps AE_RXD_UPDATE set (faulty silicon, errant DMA, or a malicious
 * PCIe passthrough NIC — the same device model as DF-1452) keeps
 * ae_intr() inside this loop forever holding ifp->if_serializer
 * (ASSERT_SERIALIZED at :754): every "frame" costs an m_devget + full
 * if_input while the mailbox write that would re-arm RX never happens.
 * One CPU is consumed, the serializer is never dropped, and ae0 (plus
 * anything that takes ifp->if_serializer) wedges until reboot —
 * livelock DoS, no privilege path.
 *
 * This model simulates the walk against a device model that re-sets
 * AE_RXD_UPDATE on the next descriptor every time the driver wraps the
 * ring, self-capped at 1,000,000 processed descriptors so the model
 * terminates.  It also demonstrates the benign (honest-device) case,
 * which terminates in ring-size steps.
 */
#include <stdio.h>
#include <stdint.h>

#define	AE_RXD_COUNT_DEFAULT	64
#define	AE_RXD_UPDATE		0x8000
#define	AE_RXD_SUCCESS		0x0001

struct ae_rxd_model {
	uint16_t	len;
	uint16_t	flags;
};

static struct ae_rxd_model ring[AE_RXD_COUNT_DEFAULT];
static unsigned int rxd_cur;
static unsigned long device_writes;	/* device re-marking UPDATE */

static void
hostile_device_mark_next(void)
{
	/* the controller DMA-writes AE_RXD_UPDATE into the descriptor the
	 * driver is about to look at (faulty or malicious device) */
	ring[rxd_cur].flags |= AE_RXD_UPDATE;
	ring[rxd_cur].len = 1518;
	device_writes++;
}

int
main(void)
{
	unsigned long processed;
	unsigned long budget_used_by_driver;

	printf("DF-4219 model: ae_rx_intr() for(;;) walk vs device-fed "
	    "AE_RXD_UPDATE\n(sys/dev/netif/ae/if_ae.c:643-668, ring=%d)\n\n",
	    AE_RXD_COUNT_DEFAULT);

	/* [1] honest device: marks exactly the frames it really received */
	for (rxd_cur = 0; rxd_cur < 5; rxd_cur++)
		ring[rxd_cur].flags = AE_RXD_UPDATE | AE_RXD_SUCCESS;
	processed = 0;
	rxd_cur = 0;
	for (;;) {
		uint16_t flags = ring[rxd_cur].flags;	/* :646 */
		if ((flags & AE_RXD_UPDATE) == 0)	/* :647 */
			break;
		ring[rxd_cur].flags = flags & ~AE_RXD_UPDATE;	/* :649 */
		rxd_cur = (rxd_cur + 1) % AE_RXD_COUNT_DEFAULT;	/* :657 */
		processed++;				/* ae_rxeof() */
	}
	printf("[1] honest device (5 real frames): walk processed %lu "
	    "descriptors, exited normally.\n\n", processed);

	/* [2] device keeps marking: walk never finds an unmarked descriptor */
	processed = 0;
	rxd_cur = 0;
	device_writes = 0;
	budget_used_by_driver = 0;	/* there is none -- counter stays 0 */
	for (;;) {
		uint16_t flags;
		hostile_device_mark_next();		/* concurrent DMA */
		flags = ring[rxd_cur].flags;		/* :646 */
		if ((flags & AE_RXD_UPDATE) == 0)	/* :647 */
			break;
		ring[rxd_cur].flags = flags & ~AE_RXD_UPDATE;	/* :649 */
		rxd_cur = (rxd_cur + 1) % AE_RXD_COUNT_DEFAULT;	/* :657 */
		processed++;				/* ae_rxeof() + if_input */
		if (processed >= 1000000UL) {
			printf("[2] device keeps AE_RXD_UPDATE ahead of the "
			    "clear: MODEL SELF-CAP HIT\n    after %lu "
			    "descriptor iterations in ONE ae_rx_intr() call "
			    "(%lu device marks,\n    ring wrapped %lu times); "
			    "guard :647 never went false.\n",
			    processed, device_writes,
			    processed / AE_RXD_COUNT_DEFAULT);
			break;
		}
	}
	printf("    frame budget consulted by driver: %lu (none exists in "
	    "if_ae.c -- ale(4) at least has dead ALE_PROC_* knobs)\n",
	    budget_used_by_driver);
	printf("    while looping: ifp->if_serializer held (:754), "
	    "AE_MB_RXD_IDX_REG mailbox (:671) never updated,\n"
	    "    every iteration m_devget+if_input (:616/:625) -> "
	    "CPU + softint livelock until reboot.\n\n");

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