DF-5220 / repro_kld.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 | /* * DF-5220 mechanism demonstration (no GPU required). * * Reproduces the corruption primitive behind the i915 engine-timeline * use-after-free WITHOUT any Intel graphics hardware: * * 1. objects A and C are linked into a private intrusive list * (mirrors list_add(&engine->timeline.link, >.timelines) * at sys/dev/drm/i915/i915_timeline.c:28, with the * kernel-context ring timeline node as the second entry) * 2. A is kfree()d while still linked * (mirrors kfree(engine) at sys/dev/drm/i915/intel_engine_cs.c:417 * executed while engine->timeline.link is live on gt.timelines, * because the execlists init error path never calls * intel_engine_cleanup_common / i915_timeline_fini) * 3. the freed chunk is reclaimed by an unrelated allocation D (slab LIFO) * 4. unlinking the NEIGHBOR C writes a pointer into D * (mirrors i915_timeline_fini()'s list_del at * sys/dev/drm/i915/i915_timeline.c:75, executed via * i915_gem_contexts_fini() on the kernel-context ring timeline * during the i915_gem_init error unwind) * * Success criterion: D (a zeroed, reallocated chunk) contains a non-NULL * pointer at the offset of A's le_prev slot after step 4 -> proof that * unlinking a neighbor performs a write into reallocated freed memory. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/queue.h> MALLOC_DEFINE(M_DF5220, "df5220", "DF-5220 list-neighbor write demo"); struct obj { char pad[64]; LIST_ENTRY(obj) link; /* le_next at +64, le_prev at +72 */ char tail[64]; }; #define ATTEMPTS 4 static LIST_HEAD(listhead, obj) head; static int df5220_modevent(module_t mod, int type, void *arg) { int i; int proven = 0; switch (type) { case MOD_LOAD: for (i = 0; i < ATTEMPTS && !proven; i++) { struct obj *a, *c, *d; void **slot; a = kmalloc(sizeof(*a), M_DF5220, M_WAITOK | M_ZERO); c = kmalloc(sizeof(*c), M_DF5220, M_WAITOK | M_ZERO); /* link A, then C at head: head -> C -> A */ LIST_INSERT_HEAD(&head, a, link); LIST_INSERT_HEAD(&head, c, link); kprintf("df5220[%d]: A=%p C=%p A.le_prev=%p " "C.le_next=%p\n", i, a, c, a->link.le_prev, c->link.le_next); /* BUG MIRROR: kfree(A) while A is still linked */ kfree(a, M_DF5220); /* unrelated allocation reclaims the freed chunk */ d = kmalloc(sizeof(*d), M_DF5220, M_WAITOK | M_ZERO); kprintf("df5220[%d]: D=%p (reclaim of A: %s)\n", i, d, (d == a) ? "HIT" : "MISS"); /* * NEIGHBOR UNLINK. LIST_REMOVE(C) executes * C->link.le_next->le_prev = C->link.le_prev; * C.le_next == &A.link (freed) -> pointer write into * the reclaimed chunk at offset 72. */ LIST_REMOVE(c, link); slot = (void **)((char *)d + offsetof(struct obj, link)); kprintf("df5220[%d]: after LIST_REMOVE(C): " "D.le_next=%p D.le_prev=%p\n", i, slot[0], slot[1]); if (d == a && slot[1] != NULL) { kprintf("df5220[%d]: VULN-WRITE PROVEN: " "pointer %p written into reallocated " "chunk D at offset %d after kfree(A)\n", i, slot[1], (int)(offsetof(struct obj, link) + sizeof(void *))); proven = 1; } kfree(c, M_DF5220); kfree(d, M_DF5220); } if (!proven) kprintf("df5220: INCONCLUSIVE (reclaim miss x%d)\n", ATTEMPTS); else kprintf("df5220: mechanism demonstrated: neighbor " "unlink writes into freed-and-reallocated " "memory\n"); return (0); case MOD_UNLOAD: return (0); default: return (EOPNOTSUPP); } } static moduledata_t df5220_mod = { "df5220", df5220_modevent, 0 }; DECLARE_MODULE(df5220, df5220_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); |