DF-5235 / compbug.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 | /* * DF-5235 โ proof of concept * * MAX_SCHEDULE_TIMEOUT (LONG_MAX) truncates to -1 when passed to * lksleep()'s `int timo` inside __wait_for_completion_generic() * (sys/dev/drm/include/linux/completion.h:90). _callout_setup_quick() * clamps a non-positive tick delta to the NEXT tick * (sys/kern/kern_timeout.c:971-972), so the "infinite" completion wait * times out after ~1 tick (10 ms at hz=100) and returns success * without the completion ever having fired. * * This module compiles the REAL upstream entry points (a verbatim copy * of sys/dev/drm/linux_completion.c โ sha256 recorded in README.md) * against the REAL completion.h from the guest's /usr/src. * * Build: make (see build.sh) * Run: kldload ./compbug.ko ; dmesg | tail (see run.sh) * * Expected on the unpatched kernel: * compbug[1] interruptible: ret=0 elapsed=1..2 ticks -> BUG * compbug[2] wait_for_completion: elapsed=1..2 ticks -> BUG * compbug[3] control (timeout=10 ticks, no completer): ret=0 * elapsed=~10..12 ticks -> OK * * Expected with fix.diff applied (timeout>=INT_MAX -> timo=0): * compbug[1]/[2]: elapsed ~= 500 ticks (5 s, the completer) -> OK */ #include <sys/param.h> #include <sys/module.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/callout.h> #include <linux/completion.h> /* * Linkage-only stubs. linux_wait.c (compiled in verbatim for * __init_waitqueue_head) also contains the wait_event-family helpers, * which touch `current' (asm/current.h) and thus drag in * linux_task_alloc/linux_task_drop/linux_proc_drop from linux_sched.c. * The completion path under test never executes any of them; these * stubs exist purely to satisfy the module linker. */ struct task_struct *linux_task_alloc(struct thread *td) { return NULL; } void linux_task_drop(struct thread *td) { } void linux_proc_drop(struct proc *p) { } static struct completion comp_i; /* interruptible, MAX_SCHEDULE_TIMEOUT */ static struct completion comp_w; /* wait_for_completion() (void) */ static struct completion comp_t; /* control: explicit small timeout */ static struct callout co_i, co_w, co_dbg; static void completer_i(void *x) { kprintf("compbug: completer_i fired at tick %d\n", ticks); complete(&comp_i); } static void completer_w(void *x) { kprintf("compbug: completer_w fired at tick %d\n", ticks); complete(&comp_w); } static void dbg_tick(void *x) { kprintf("compbug: still waiting at tick %d (bug not present?)\n", ticks); } static int compbug_modevent(module_t mod, int type, void *data) { int t0, t1, ret; long rt; switch (type) { case MOD_LOAD: init_completion(&comp_i); init_completion(&comp_w); init_completion(&comp_t); callout_init(&co_i); callout_init(&co_w); callout_init(&co_dbg); /* * Case 1 โ wait_for_completion_interruptible(): * completer scheduled 5 s (500 ticks) out. Correct * behavior: block ~500 ticks, ret 0. Buggy: ret 0 * after ~1 tick. */ t0 = ticks; callout_reset(&co_i, 5 * hz, completer_i, NULL); callout_reset(&co_dbg, 2 * hz, dbg_tick, NULL); ret = wait_for_completion_interruptible(&comp_i); t1 = ticks; callout_stop(&co_i); callout_stop(&co_dbg); kprintf("compbug[1] interruptible: ret=%d elapsed=%d ticks " "(completer due at %d): %s\n", ret, t1 - t0, 5 * hz, ((t1 - t0) < 4 * hz) ? "BUG: returned before completion" : "OK"); /* * Case 2 โ wait_for_completion(): completer 5 s out. * Correct: block ~500 ticks. Buggy: ~1 tick. */ t0 = ticks; callout_reset(&co_w, 5 * hz, completer_w, NULL); callout_reset(&co_dbg, 2 * hz, dbg_tick, NULL); wait_for_completion(&comp_w); t1 = ticks; callout_stop(&co_w); callout_stop(&co_dbg); kprintf("compbug[2] wait_for_completion: elapsed=%d ticks " "(completer due at %d): %s\n", t1 - t0, 5 * hz, ((t1 - t0) < 4 * hz) ? "BUG: returned before completion" : "OK"); /* * Case 3 โ control: explicit timeout of 10 ticks, no * completer. timeout fits in int, must NOT be affected * by the truncation: ret==0, elapsed ~10..13. */ t0 = ticks; rt = wait_for_completion_timeout(&comp_t, 10); t1 = ticks; kprintf("compbug[3] control timeout=10: ret=%ld elapsed=%d " "ticks: %s\n", rt, t1 - t0, (rt == 0 && (t1 - t0) >= 9 && (t1 - t0) <= 14) ? "OK" : "UNEXPECTED"); kprintf("compbug: hz=%d, LONG_MAX=%jd -> int timo=%d " "(LKSLEEP SEES %d)\n", hz, (intmax_t)LONG_MAX, (int)LONG_MAX, (int)LONG_MAX); return (0); case MOD_UNLOAD: return (0); default: return (EOPNOTSUPP); } } static moduledata_t compbug_mod = { "compbug", compbug_modevent, NULL }; DECLARE_MODULE(compbug, compbug_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(compbug, 1); |