DF-0546 / 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 | /* * DF-0546 harness: netmap_mem_ofstophys OOB read of lut[] * * Source: sys/net/netmap/netmap_mem2.c:165-195 (DEAD CODE โ netmap is NOT * compiled on DragonFlyBSD master; not listed in sys/conf/files) * * The bug: * netmap_mem_ofstophys(:165-195): * for (i = 0; i < NETMAP_POOLS_NR; offset -= p[i].memtotal, i++) { * if (offset >= p[i].memtotal) * continue; * pa = p[i].lut[offset / p[i]._objsize].paddr + // OOB index! * offset % p[i]._objsize; * * memtotal = numclusters * _clustsize (:622), where _clustsize is rounded * UP to PAGE_SIZE (:531-533). When clustentries * _objsize is not a page * multiple, memtotal > objtotal * _objsize. * * An offset in [objtotal*_objsize, memtotal) passes the `offset < memtotal` * check but produces lut index = offset/_objsize >= objtotal -> OOB read * past the lut[] array (allocated with objtotal entries at :565). * * Result: garbage paddr interpreted as physical address -> vm_page_getfake * maps an arbitrary physical page RW into the faulting process. * * Since netmap is dead code on master, this harness demonstrates the OOB * indexing logic in userspace with controlled pool parameters. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define PAGE_SIZE 4096 struct lut_entry { uint64_t paddr; }; struct netmap_obj_pool { uint32_t _objsize; /* object size */ uint32_t _clustsize; /* cluster size, rounded to PAGE_SIZE */ uint32_t numclusters; /* number of clusters */ uint32_t clustentries; /* objects per cluster */ uint32_t objtotal; /* total objects = numclusters * clustentries */ uint64_t memtotal; /* = numclusters * _clustsize */ struct lut_entry *lut; /* allocated with objtotal entries */ }; /* Replicates netmap_mem2.c:531-533 _clustsize rounding */ static uint32_t round_to_page(uint32_t sz) { if (sz < PAGE_SIZE) return PAGE_SIZE; return (sz + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); } /* Setup a pool where memtotal > objtotal * _objsize */ static void init_pool(struct netmap_obj_pool *p, uint32_t objsize) { p->_objsize = objsize; p->clustentries = PAGE_SIZE / objsize; /* objects per page */ if (p->clustentries == 0) p->clustentries = 1; /* _clustsize = round_up(clustentries * objsize, PAGE_SIZE) */ uint32_t raw_clustsize = p->clustentries * objsize; p->_clustsize = round_to_page(raw_clustsize); p->numclusters = 16; /* arbitrary */ p->objtotal = p->numclusters * p->clustentries; p->memtotal = (uint64_t)p->numclusters * p->_clustsize; p->lut = calloc(p->objtotal, sizeof(struct lut_entry)); /* Fill lut with recognizable addresses */ for (uint32_t i = 0; i < p->objtotal; i++) p->lut[i].paddr = 0x10000000ULL + i * objsize; } /* Replicates netmap_mem_ofstophys (netmap_mem2.c:165-195) */ static uint64_t mem_ofstophys(struct netmap_obj_pool *p, uint64_t offset) { if (offset >= p->memtotal) return 0; /* error path */ uint32_t idx = offset / p->_objsize; if (idx >= p->objtotal) { /* OOB READ โ reads past lut[] array */ printf(" -> lut index %u >= objtotal %u -> OOB READ!\n", idx, p->objtotal); printf(" -> reads %ld bytes past lut array end\n", (long)((idx - p->objtotal) * sizeof(struct lut_entry))); /* In kernel: reads whatever is adjacent in the slab */ return 0xDEADBEEF; /* garbage */ } return p->lut[idx].paddr + (offset % p->_objsize); } int main(void) { printf("=== DF-0546: netmap_mem_ofstophys lut[] OOB read ===\n\n"); printf("NOTE: netmap is NOT compiled on DragonFlyBSD master.\n"); printf(" Bug confirmed by source trace; harness demonstrates the logic.\n\n"); /* Use objsize=2048: clustentries = 4096/2048 = 2, raw=4096, _clustsize=4096 */ /* objtotal * objsize = 16*2 * 2048 = 65536; memtotal = 16*4096 = 65536 */ /* This packs cleanly โ no OOB. Need a non-page-multiple case. */ /* Use objsize=1500: clustentries = 4096/1500 = 2, raw=3000 */ /* _clustsize = round_up(3000) = 4096 */ /* objtotal * objsize = 32 * 1500 = 48000; memtotal = 16*4096 = 65536 */ /* memtotal (65536) > objtotal*objsize (48000) โ OOB window exists! */ struct netmap_obj_pool pool; init_pool(&pool, 1500); printf("Pool parameters (objsize=1500):\n"); printf(" _objsize = %u\n", pool._objsize); printf(" _clustsize = %u (rounded up from %u)\n", pool._clustsize, pool.clustentries * pool._objsize); printf(" clustentries = %u\n", pool.clustentries); printf(" numclusters = %u\n", pool.numclusters); printf(" objtotal = %u (lut has this many entries)\n", pool.objtotal); printf(" memtotal = %lu (offset validated against this)\n", (unsigned long)pool.memtotal); printf(" objtotal*objsize = %lu\n", (unsigned long)pool.objtotal * pool._objsize); printf(" OOB window = [%lu, %lu) = %lu bytes\n\n", (unsigned long)pool.objtotal * pool._objsize, (unsigned long)pool.memtotal, (unsigned long)pool.memtotal - (unsigned long)pool.objtotal * pool._objsize); /* Test: offset just before OOB window (valid) */ uint64_t off_valid = (uint64_t)(pool.objtotal - 1) * pool._objsize; printf("[1] Valid offset %lu:\n", (unsigned long)off_valid); uint64_t pa = mem_ofstophys(&pool, off_valid); printf(" -> pa = 0x%lx (valid)\n\n", (unsigned long)pa); /* Test: offset in OOB window */ uint64_t off_oob = (uint64_t)pool.objtotal * pool._objsize + 100; printf("[2] OOB offset %lu (in padding window):\n", (unsigned long)off_oob); printf(" offset < memtotal (%lu < %lu)? %s\n", (unsigned long)off_oob, (unsigned long)pool.memtotal, off_oob < pool.memtotal ? "YES โ passes check" : "NO"); pa = mem_ofstophys(&pool, off_oob); printf(" -> pa = 0x%lx (GARBAGE from adjacent slab heap)\n\n", (unsigned long)pa); printf("=== BUG CONFIRMED ===\n"); printf("Offset in [objtotal*_objsize, memtotal) passes validation\n"); printf("but produces lut index >= objtotal -> OOB read of adjacent heap.\n"); printf("Garbage paddr mapped into userspace -> info leak / arbitrary phys R/W.\n"); free(pool.lut); return 0; } |