DF-5248 / drain_fifo_loop.c
/* * DF-5248 - run_drain_fifo() unbounded loop on device-controlled/dataless * TX_STAT_FIFO reads if_run.c:2503-2563 * ===================================================================== * static void * run_drain_fifo(void *arg) * { * for (;;) { * run_read(sc, RT2860_TX_STAT_FIFO, &stat); <-- 2515 RETURN * if (!(stat & RT2860_TXQ_VLD)) <-- VALUE IGNORED * break; * ... * } * } * * RT2860_TXQ_VLD == (1 << 0) if_runreg.h:664 * run_read() on USB failure sets *val = 0xffffffff if_run.c:1254 * * => every failed control read yields 0xffffffff which HAS bit0 set * (and wcid 0xff > RT2870_WCID_MAX so the `continue' path is taken), * so a device that stops responding (unplug, hostile stall) or a * hostile device that always returns VLD makes the loop unbounded. * Each iteration burns up to 10 x 250 ms of control-request retries * (run_do_request, if_run.c:1232-1240), wedging the ieee80211 task * forever; run_detach()'s ieee80211_draintask() (if_run.c:879-880) * then blocks device_detach() indefinitely. * * This harness proves the loop condition never terminates under either * condition, using the exact kernel expressions. */ #include <stdio.h> #define RT2860_TXQ_VLD (1 << 0) #define RT2860_TXQ_ACKREQ (1 << 7) #define RT2860_TXQ_WCID_SHIFT 8 #define RT2870_WCID_MAX 64 /* mock run_read(): error => *val = 0xffffffff, returns nonzero (ignored) */ static int usb_error; /* set by the scenario */ static unsigned hostile_stat; /* what a lying device returns */ static int run_read(unsigned *val) { if (usb_error) { *val = 0xffffffffu; return (-1); } *val = hostile_stat; return (0); } int main(void) { unsigned stat; unsigned char wcid; unsigned long iters; const unsigned long CAP = 1000000; /* kernel has NO cap; we cap to prove non-termination */ /* scenario 1: device unplugged / control transfer fails forever */ usb_error = 1; for (iters = 0; iters < CAP; iters++) { run_read(&stat); if (!(stat & RT2860_TXQ_VLD)) break; wcid = (stat >> RT2860_TXQ_WCID_SHIFT) & 0xff; if (!(stat & RT2860_TXQ_ACKREQ) || wcid > RT2870_WCID_MAX || wcid == 0) continue; /* loop again */ } printf("scenario 1 (persistent read error, *val=0xffffffff): " "%s after %lu iterations (cap %lu)\n", iters == CAP ? "NEVER TERMINATES" : "terminated", iters, CAP); /* scenario 2: hostile device always returns VLD */ usb_error = 0; hostile_stat = RT2860_TXQ_VLD | RT2860_TXQ_ACKREQ | (0x42u << RT2860_TXQ_WCID_SHIFT); for (iters = 0; iters < CAP; iters++) { run_read(&stat); if (!(stat & RT2860_TXQ_VLD)) break; wcid = (stat >> RT2860_TXQ_WCID_SHIFT) & 0xff; if (!(stat & RT2860_TXQ_ACKREQ) || wcid > RT2870_WCID_MAX || wcid == 0) continue; } printf("scenario 2 (hostile device, VLD always set): " "%s after %lu iterations (cap %lu)\n", iters == CAP ? "NEVER TERMINATES" : "terminated", iters, CAP); return 0; } |