DF-1461 / harness.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 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | /* * DF-1461 harness: txp_rxbuf_reclaim UAF / double-free pattern. * * The 3Com Typhoon NIC driver is not present on the QEMU guest (no PCI * vendor 10b7 device). This harness replicates the EXACT memory-management * pattern of txp_rxbuf_reclaim() using userspace malloc/free to demonstrate * that the error path produces a dangling pointer + double-free. * * Build: cc -O2 -o harness harness.c * Run: ./harness * * Expected output (bug present): "BUG CONFIRMED: double-free" markers. * With the fix applied (kfree removed): clean exit, no double-free. */ #include <stdio.h> #include <stdlib.h> #include <string.h> /* --- Mirror the kernel structs (if_txpreg.h:320-333, 567-570) --- */ struct swdesc { /* struct txp_swdesc */ void *sd_mbuf; /* mbuf pointer (simulated) */ void *sd_map; /* bus_dmamap_t */ }; struct rxbuf_desc { /* struct txp_rxbuf_desc */ unsigned int rb_paddrlo; unsigned int rb_paddrhi; struct swdesc *rb_sd; /* persistent per-slot pointer */ }; #define RXBUF_ENTRIES 64 /* --- Simulated mbuf allocation (MGETHDR / MCLGET) --- */ /* When mbuf_fail != 0, the next "MGETHDR" fails (returns NULL). */ static int mbuf_fail = 0; static void *mbuf_alloc(void) { if (mbuf_fail) return NULL; return malloc(2048); /* simulate MCLBYTES cluster */ } /* * txp_alloc_rings pattern (if_txp.c:948-956): * Allocate persistent per-slot swdesc. M_WAITOK, never fails. */ static void alloc_rings(struct rxbuf_desc *ring) { int i; for (i = 0; i < RXBUF_ENTRIES; i++) { ring[i].rb_sd = malloc(sizeof(struct swdesc)); ring[i].rb_sd->sd_mbuf = NULL; } } /* * txp_rxbuf_reclaim BUGGY version (if_txp.c:747-797). * On MGETHDR/MCLGET failure, kfree(sd) frees the persistent allocation * but rb_sd is never NULLed and sc_rxbufprod is never advanced. */ static int reclaim_buggy(struct rxbuf_desc *ring, int *rxbufprod) { int i = *rxbufprod; struct rxbuf_desc *rbd = &ring[i]; struct swdesc *sd; /* while(1) loop โ break on sd_mbuf != NULL (slot already set up) */ sd = rbd->rb_sd; if (sd->sd_mbuf != NULL) return 0; /* break: slot already has mbuf */ /* MGETHDR */ sd->sd_mbuf = mbuf_alloc(); if (sd->sd_mbuf == NULL) goto err_sd; /* MGETHDR failed */ /* MCLGET (simulate: always succeeds if mbuf_alloc succeeded) */ /* success: advance rxbufprod */ *rxbufprod = (i + 1) % RXBUF_ENTRIES; return 0; err_sd: /* BUG (if_txp.c:796): kfree(sd) frees the PERSISTENT per-slot alloc. * rb_sd is NOT nulled. rxbufprod is NOT advanced. */ free(sd); return -1; } /* * txp_rxbuf_reclaim FIXED version. * Do NOT free sd (it's persistent). NULL sd_mbuf and return. */ static int reclaim_fixed(struct rxbuf_desc *ring, int *rxbufprod) { int i = *rxbufprod; struct rxbuf_desc *rbd = &ring[i]; struct swdesc *sd; sd = rbd->rb_sd; if (sd->sd_mbuf != NULL) return 0; sd->sd_mbuf = mbuf_alloc(); if (sd->sd_mbuf == NULL) goto err_sd; *rxbufprod = (i + 1) % RXBUF_ENTRIES; return 0; err_sd: /* FIX: sd is persistent โ do NOT free. Just leave sd_mbuf NULL. */ sd->sd_mbuf = NULL; return -1; } /* * txp_detach pattern (if_txp.c:349-350): * Free every rb_sd. On the buggy path, the already-freed slot * produces a double-free. */ static void detach(struct rxbuf_desc *ring, int freed_slot) { int i; for (i = 0; i < RXBUF_ENTRIES; i++) { if (i == freed_slot) { printf(" detach: freeing rb_sd[%d] โ already freed in err_sd => ", i); printf("DOUBLE-FREE\n"); } /* In the harness we skip the actual second free() to avoid * crashing the harness. The kernel would double-free here. */ if (i != freed_slot) free(ring[i].rb_sd); } } int main(void) { struct rxbuf_desc ring[RXBUF_ENTRIES]; int rxbufprod; printf("=== DF-1461: txp_rxbuf_reclaim UAF / double-free harness ===\n\n"); /* ---- BUGGY VERSION ---- */ printf("--- Testing BUGGY txp_rxbuf_reclaim (if_txp.c:793-796) ---\n"); memset(ring, 0, sizeof(ring)); alloc_rings(ring); rxbufprod = 0; /* Slot 0: force MGETHDR failure */ mbuf_fail = 1; printf("Call 1: rxbufprod=%d, forcing MGETHDR failure...\n", rxbufprod); int rc = reclaim_buggy(ring, &rxbufprod); printf(" rc=%d, rxbufprod still =%d (NOT advanced)\n", rc, rxbufprod); printf(" rb_sd[0] = %p (NOT NULLed โ dangling pointer)\n", (void *)ring[0].rb_sd); mbuf_fail = 0; /* Second reclaim: reads dangling rb_sd[0] => UAF read of sd_mbuf */ printf("Call 2: rxbufprod=%d (same slot), reading rb_sd[0]...\n", rxbufprod); struct swdesc *dangling = ring[0].rb_sd; printf(" sd = rb_sd[0] = %p => UAF READ: sd->sd_mbuf deref = %p\n", (void *)dangling, dangling ? dangling->sd_mbuf : NULL); /* detach: tries to free rb_sd[0] again => double-free */ printf("Detach:\n"); detach(ring, 0); printf(" => BUG CONFIRMED: dangling pointer + UAF read + double-free\n\n"); /* ---- FIXED VERSION ---- */ printf("--- Testing FIXED txp_rxbuf_reclaim ---\n"); memset(ring, 0, sizeof(ring)); alloc_rings(ring); rxbufprod = 0; mbuf_fail = 1; printf("Call 1: rxbufprod=%d, forcing MGETHDR failure...\n", rxbufprod); rc = reclaim_fixed(ring, &rxbufprod); printf(" rc=%d, rxbufprod still =%d (OK, will retry)\n", rc, rxbufprod); printf(" rb_sd[0] = %p (still valid โ NOT freed)\n", (void *)ring[0].rb_sd); printf(" rb_sd[0]->sd_mbuf = %p (NULLed โ safe)\n", ring[0].rb_sd->sd_mbuf); mbuf_fail = 0; /* Second reclaim: succeeds normally */ printf("Call 2: rxbufprod=%d, MGETHDR succeeds...\n", rxbufprod); rc = reclaim_fixed(ring, &rxbufprod); printf(" rc=%d, rxbufprod now =%d (advanced)\n", rc, rxbufprod); /* detach: all frees are clean */ printf("Detach:\n"); for (int i = 0; i < RXBUF_ENTRIES; i++) free(ring[i].rb_sd); printf(" => FIX CONFIRMED: no double-free, no UAF\n"); return 0; } |