DF-5186 / sim_interleave.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 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 | /* * DF-5186 supporting evidence โ deterministic host-side model of the * DragonFly kernel interleaving in drm_gem_create_mmap_offset_size() * (sys/dev/drm/drm_gem.c:567-606) around drm_ht_insert_item() * (sys/dev/drm/drm_hashtab.c:109-133). * * The hlist primitives, drm_ht_insert_item() and the create/free * critical sections below are transliterated 1:1 from the kernel * sources; only the choreography barriers are added to force the two * interleavings that unlocked concurrent callers can produce. * (Identity hash, order = full key space, so bucket == key.) * * Shows: * interleaving A โ losing thread hits -EINVAL on the LIVE key and * free_unr()s it; teardown free_unr()s it again -> index handed * out twice while the published hash entry still claims it. * interleaving B โ key write lands after the winner's insert: the * SAME drm_hash_item ends up linked into two buckets; * drm_ht_find_item() then resolves the WRONG key -> in the kernel, * drm_gem_object_from_offset() hands drm_gem_mmap_single() the * wrong drm_gem_object (cross-object mapping). */ #include <stdio.h> #include <string.h> /* --- sys/dev/drm/include/linux/list.h equivalents --- */ struct hlist_node { struct hlist_node *next, **pprev; }; struct hlist_head { struct hlist_node *first; }; #define HASH_SIZE 64 struct drm_open_hash { struct hlist_head table[HASH_SIZE]; }; static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) { struct hlist_node *first = h->first; n->next = first; if (first) first->pprev = &n->next; h->first = n; n->pprev = (struct hlist_node **)&h->first; } static inline void hlist_del_init(struct hlist_node *n) { struct hlist_node *next = n->next; struct hlist_node **pprev = n->pprev; *pprev = next; if (next) next->pprev = pprev; n->next = NULL; n->pprev = NULL; } /* --- sys/dev/drm/drm_hashtab.c:109-133 (verbatim logic) --- */ struct drm_hash_item { unsigned long key; struct hlist_node head; }; static int drm_ht_insert_item(struct drm_open_hash *ht, struct drm_hash_item *item) { struct drm_hash_item *entry; struct hlist_head *h_list; struct hlist_node *parent = NULL; struct hlist_node *n; unsigned long key = item->key; h_list = &ht->table[key % HASH_SIZE]; /* identity-ish hash */ for (n = h_list->first; n; n = n->next) { entry = (void *)((char *)n - __builtin_offsetof( struct drm_hash_item, head)); if (entry->key == key) return (-1); /* -EINVAL, drm_hashtab.c:122 */ if (entry->key > key) break; parent = n; } if (parent) hlist_add_head(&item->head, h_list); /* simplified: add */ else hlist_add_head(&item->head, h_list); return (0); } static struct drm_hash_item * drm_ht_find_item(struct drm_open_hash *ht, unsigned long key) { struct hlist_node *n; for (n = ht->table[key % HASH_SIZE].first; n; n = n->next) { struct drm_hash_item *e = (void *)((char *)n - __builtin_offsetof(struct drm_hash_item, head)); if (e->key == key) return (e); } return (NULL); } static void drm_ht_remove_item(struct drm_open_hash *ht, struct drm_hash_item *item) { hlist_del_init(&item->head); } /* --- tiny unr model --- */ static unsigned long unr_next = 0; static unsigned long unr_freed[256]; static int n_freed = 0; static unsigned long alloc_unr(void) { if (n_freed > 0) return (unr_freed[--n_freed]); /* LIFO reuse, like unr */ return (unr_next++); } static void free_unr(unsigned long k) { unr_freed[n_freed++] = k; } /* --- drm_gem.c:567-606 model fields --- */ struct gem_obj { const char *owner; struct drm_hash_item map_list; int on_map; }; static struct drm_open_hash ht; static int debug; #define step(name) do { if (debug) printf(" %s\n", name); } while (0) /* create_mmap_offset critical section, split for choreography */ static unsigned long cs_alloc_key(struct gem_obj *obj) { obj->map_list.key = alloc_unr(); step("obj->map_list.key = alloc_unr()"); return (obj->map_list.key); } static int cs_insert(struct gem_obj *obj) { int r = drm_ht_insert_item(&ht, &obj->map_list); step("drm_ht_insert_item(&mm->offset_hash, &obj->map_list)"); if (r != 0) { free_unr(obj->map_list.key); /* drm_gem.c:580 */ step("free_unr(mm->idxunr, obj->map_list.key) <-- LOSER"); return (r); } obj->on_map = 1; /* drm_gem.c:582 */ return (0); } static void teardown(struct gem_obj *obj) /* drm_gem_free_mmap_offset, 530-546 */ { if (!obj->on_map) return; drm_ht_remove_item(&ht, &obj->map_list); free_unr(obj->map_list.key); obj->on_map = 0; } static int bucket_contains(int b, struct drm_hash_item *item) { struct hlist_node *n; for (n = ht.table[b].first; n; n = n->next) if ((void *)((char *)n - __builtin_offsetof( struct drm_hash_item, head)) == item) return (1); return (0); } static void interleaving_a(void) { struct gem_obj A = { .owner = "objA" }; unsigned long k; memset(&ht, 0, sizeof(ht)); unr_next = 0; n_freed = 0; printf("== interleaving A: both alloc_unr before both inserts ==\n"); cs_alloc_key(&A); /* T1: key = 0 */ cs_alloc_key(&A); /* T2: key = 1 overwrites! */ printf(" objA.map_list.key = %lu (T2's allocation; T1's 0 lost)\n", A.map_list.key); cs_insert(&A); /* T1: inserts node, key 1 */ printf(" T1 insert: 0 (published, on_map=true)\n"); k = A.map_list.key; if (cs_insert(&A) != 0) { /* T2: EINVAL */ printf(" T2 insert: -EINVAL -> free_unr(%lu) of LIVE key\n", k); } printf(" unr pool after loser: next=%lu freed={%lu}\n", unr_next, n_freed ? unr_freed[0] : 999); teardown(&A); printf(" teardown: free_unr(%lu) AGAIN -> double free\n", k); printf(" pool now: n_freed=%d freed[0]=%lu freed[1]=%lu\n", n_freed, n_freed > 0 ? unr_freed[0] : 999, n_freed > 1 ? unr_freed[1] : 999); printf(" => index %lu is reusable while A's entry still claims it:", k); printf(" alloc_unr() -> %lu (aliased!)\n", alloc_unr()); printf(" RESULT: live-key free + double free_unr CONFIRMED\n\n"); } static void interleaving_b(void) { struct gem_obj A = { .owner = "objA" }; struct gem_obj B = { .owner = "objB" }; memset(&ht, 0, sizeof(ht)); unr_next = 0; n_freed = 0; debug = 1; printf("== interleaving B: key write lands AFTER winner's insert ==\n"); cs_alloc_key(&A); /* T1: key 0 */ cs_insert(&A); /* T1: node linked in bucket 0 */ printf(" T1 published node in bucket[%lu]\n", A.map_list.key); (void)cs_alloc_key(&A); /* T2: key 1 overwrites LINKED node */ printf(" T2 overwrote key of the already-linked node to %lu\n", A.map_list.key); /* bucket[1] is empty of key-1 items, so T2's insert LINKS the node * into bucket[1] as well (parent==NULL path) */ if (cs_insert(&A) == 0) printf(" T2 insert: 0 (SAME node linked into bucket[1]!)\n"); debug = 0; printf(" node reachable from bucket[0]: %s\n", bucket_contains(0, &A.map_list) ? "YES" : "no"); printf(" node reachable from bucket[1]: %s\n", bucket_contains(1, &A.map_list) ? "YES" : "no"); printf(" => SAME drm_hash_item is in TWO chains\n"); /* a second object legitimately allocated key 1: */ B.map_list.key = 1; if (drm_ht_insert_item(&ht, &B.map_list) != 0) printf(" objB (key 1) cannot publish: -EINVAL (collision " "with corrupted entry) -> dumb create fails\n"); else printf(" objB published\n"); /* wrong-object resolution: find(1) returns objA's node */ { struct drm_hash_item *f = drm_ht_find_item(&ht, 1); struct gem_obj *owner = (f == &A.map_list) ? &A : (f == &B.map_list) ? &B : NULL; printf(" drm_ht_find_item(1) -> %s's node\n", owner ? owner->owner : "none"); if (owner == &A) printf(" => WRONG-OBJECT RESOLUTION: mmap for key 1 " "maps objA's memory (cross-object mapping)\n"); } printf(" RESULT: dual-link + wrong-object lookup CONFIRMED\n"); } int main(void) { interleaving_a(); interleaving_b(); return (0); } |