DF-4219 / trigger.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | /* * 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); } |