DF-3757 / txput_setup_guard_model.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 131 132 133 134 135 136 137 138 | /* * 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); } |