DF-4625 / df4625.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 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 | /* * DF-4625 KLD — repro for: * output_poll_execute()'s self-rearm structurally survives * drm_kms_helper_poll_disable()/poll_fini() because the DragonFly * drm workqueue shim's cancel_delayed_work_sync() * (sys/dev/drm/linux_workqueue.c:415-424): * 1. cancels the callout BEFORE waiting out the running callback, * 2. the callback's schedule_delayed_work() (drm_probe_helper.c:654) * then arms a FRESH timer nothing ever cancels, * 3. _cancel_work() (:348-385) breaks as soon as the work is idle -- * a pending timer is invisible to it, * 4. when the timer fires, queue_work() CLEARS work->canceled * (linux_workqueue.c:135) and the full poll cycle runs again. * * This module compiles the shim's functions VERBATIM (build.sh seds them * out of the guest's /usr/src/sys/dev/drm/linux_workqueue.c) against the * REAL kernel primitives (lockmgr, lksleep, callout_reset/callout_cancel, * lwkt threads) and then races a self-rearming delayed_work exactly the * way drm_probe_helper.c's poll worker does. * * Build the FIXED variant with -DDF4625_FIXED (see build.sh) which only * replaces cancel_delayed_work_sync() with the converging loop. * * The module refuses to unload (its worker thread loops forever — the * same DF-1977 wart); reboot the guest afterwards. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/module.h> #include <sys/malloc.h> #include <sys/queue.h> #include <sys/lock.h> #include <sys/proc.h> #include <sys/thread.h> #include <sys/callout.h> #include <machine/cpufunc.h> MALLOC_DEFINE(M_DF4625, "df4625", "DF-4625 test allocations"); /* ---- struct environment, copied field-for-field from * sys/dev/drm/include/linux/workqueue.h ---- */ struct workqueue_worker; struct work_struct { STAILQ_ENTRY(work_struct) ws_entries; void (*func)(struct work_struct *); struct workqueue_worker *worker; bool on_queue; bool running; bool canceled; }; struct workqueue_worker { STAILQ_HEAD(ws_list, work_struct) ws_list_head; struct thread *worker_thread; struct lock worker_lock; }; struct workqueue_struct { bool is_draining; int num_workers; struct workqueue_worker (*workers)[]; }; struct delayed_work { struct work_struct work; struct callout timer; }; struct workqueue_struct *system_wq; #define INIT_WORK(work, _func) \ do { \ (work)->ws_entries.stqe_next = NULL; \ (work)->func = (_func); \ (work)->on_queue = false; \ (work)->running = false; \ (work)->canceled = false; \ } while (0) #define INIT_DELAYED_WORK(_work, _func) \ do { \ INIT_WORK(&(_work)->work, _func); \ callout_init_mp(&(_work)->timer); \ } while (0) static inline struct delayed_work * to_delayed_work(struct work_struct *work) { return (void *)((char *)work - offsetof(struct delayed_work, work)); } static inline bool schedule_delayed_work(struct delayed_work *dwork, unsigned long delay) { return queue_delayed_work(system_wq, dwork, delay); } /* ---- the shim, VERBATIM (df4625_extracted.inc, generated by build.sh): * process_all_work, wq_worker_thread, queue_work, _delayed_work_fn, * queue_delayed_work, _cancel_work, cancel_work_sync, * cancel_delayed_work, cancel_delayed_work_sync ---- */ #ifdef DF4625_FIXED #define cancel_delayed_work_sync cancel_delayed_work_sync_orig #include "df4625_extracted.inc" #undef cancel_delayed_work_sync /* Fixed variant: loop until the timer is dead AND the work is idle. */ static bool cancel_delayed_work_sync(struct delayed_work *dwork) { struct work_struct *work = &dwork->work; bool ret; do { ret = _cancel_work(work, true); } while (callout_cancel(&dwork->timer) || work->on_queue || work->running); return ret; } #else #include "df4625_extracted.inc" #endif /* ---- the race scenario ---- */ static struct delayed_work poll_work; static struct lock seq_lock; static volatile int exec_count; static volatile int cb_running; static volatile int hold_release; static volatile int cancel_done; static volatile int final_stop; static int rearm_ticks = 4; /* emulates output_poll_execute's tail (drm_probe_helper.c:653-654): * ... if (repoll) schedule_delayed_work(delayed_work, DRM_OUTPUT_POLL_PERIOD); */ static void poll_emul_fn(struct work_struct *work) { int n = ++exec_count; kprintf("df4625: EXEC #%d entered\n", n); if (final_stop) return; /* hold, so the canceller passes callout_cancel() and parks inside * _cancel_work()'s lksleep while this callback is running */ lockmgr(&seq_lock, LK_EXCLUSIVE); cb_running = 1; wakeup(&cb_running); while (!hold_release) lksleep(&hold_release, &seq_lock, 0, "hold", 1); lockmgr(&seq_lock, LK_RELEASE); kprintf("df4625: EXEC #%d re-arming (%d ticks)\n", n, rearm_ticks); schedule_delayed_work(&poll_work, rearm_ticks); } static void canceller_thread(void *arg) { kprintf("df4625: canceller: cancel_delayed_work_sync() ENTER\n"); cancel_delayed_work_sync(&poll_work); kprintf("df4625: canceller: cancel_delayed_work_sync() RETURN " "(timer pending=%d on_queue=%d running=%d)\n", callout_pending(&poll_work.timer), poll_work.work.on_queue, poll_work.work.running); lockmgr(&seq_lock, LK_EXCLUSIVE); cancel_done = 1; wakeup(&cancel_done); lockmgr(&seq_lock, LK_RELEASE); lwkt_exit(); } static void wait_flag(volatile int *p, int ticks) { int i; for (i = 0; i < ticks && !*p; i++) tsleep(p, 0, "df4625", 1); } static int df4625_runtest(void) { struct workqueue_worker *worker; thread_t td; int i, exec_at_cancel; system_wq = kmalloc(sizeof(struct workqueue_struct), M_DF4625, M_WAITOK | M_ZERO); system_wq->num_workers = 1; system_wq->workers = kmalloc(sizeof(struct workqueue_worker), M_DF4625, M_WAITOK | M_ZERO); worker = &(*system_wq->workers)[0]; lockinit(&worker->worker_lock, "df4625w", 0, 0); STAILQ_INIT(&worker->ws_list_head); if (lwkt_create(wq_worker_thread, worker, &worker->worker_thread, NULL, TDF_NOSTART, -1, "df4625w") != 0) return (ENOMEM); lwkt_setpri_initial(worker->worker_thread, TDPRI_KERN_DAEMON); lwkt_schedule(worker->worker_thread); lockinit(&seq_lock, "df4625s", 0, 0); INIT_DELAYED_WORK(&poll_work, poll_emul_fn); kprintf("df4625: === scenario: cancel_delayed_work_sync() called while " "the self-rearming callback is executing ===\n"); queue_delayed_work(system_wq, &poll_work, 1); wait_flag(&cb_running, hz); /* callback now holding */ lwkt_create(canceller_thread, NULL, &td, NULL, 0, -1, "df4625c"); /* let the canceller pass callout_cancel() and enter _cancel_work() */ tsleep(&exec_count, 0, "df4625m", 3); /* release the callback: it re-arms the timer and returns */ lockmgr(&seq_lock, LK_EXCLUSIVE); hold_release = 1; wakeup(&hold_release); lockmgr(&seq_lock, LK_RELEASE); wait_flag(&cancel_done, 10 * hz); exec_at_cancel = exec_count; kprintf("df4625: cancel returned with exec_count=%d; watching %d " "ticks for a post-cancel EXEC\n", exec_at_cancel, rearm_ticks * 3); for (i = 0; i < rearm_ticks * 3 + hz / 2; i++) { tsleep(&seq_lock, 0, "df4625p", 1); if (exec_count > exec_at_cancel) break; } if (exec_count > exec_at_cancel) { kprintf("df4625: DF-4625 %s: work executed AFTER " "cancel_delayed_work_sync() returned (exec_count %d -> %d)\n", #ifdef DF4625_FIXED "UNEXPECTED (fixed build)", #else "REPRODUCED", #endif exec_at_cancel, exec_count); } else { kprintf("df4625: %s: no execution after cancel_delayed_work_sync() " "returned within the window (exec_count=%d)\n", #ifdef DF4625_FIXED "FIX VALIDATED", #else "NOT reproduced", #endif exec_count); } /* teardown the chain; worker thread stays (module unloads never) */ final_stop = 1; cancel_delayed_work(&poll_work); callout_cancel(&poll_work.timer); return (0); } static int df4625_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: kprintf("df4625: module load (%s build)\n", #ifdef DF4625_FIXED "FIXED" #else "BASELINE" #endif ); return (df4625_runtest()); case MOD_UNLOAD: /* the verbatim wq_worker_thread loops forever (DF-1977); * never unload so its code is never unmapped */ return (EBUSY); default: return (EOPNOTSUPP); } } static moduledata_t df4625_mod = { "df4625", df4625_modevent, NULL }; DECLARE_MODULE(df4625, df4625_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); MODULE_VERSION(df4625, 1); |