wake_up_bit() ignores the bit number and wakes exactly ONE waiter on the word: wrong-waiter wakeups and N-1 stranded waiters for N concurrent waiters on one word (no per-(addr,bit) wait identity, fanout of one)
Summary
The sleep identity is the bare word pointer, the bit arg is unused, and wakeup_one has single-waiter fanout - a wake for bit A can rouse bit B's waiter (stolen wakeup). Latent today (i915's two sites are BACKOFF-serialized; the 1Hz poll self-heals). Encode-bit-into-ident + all-waiters-wake fix sketch.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5184 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | file | 2.9 KB | β raw | |
| verdict.json | file | 1.1 KB | view raw |
DF-5184 β wake_up_bit() ignores the bit number and wakes exactly ONE waiter on the word
File: sys/dev/drm/linux_wait.c:97-101
Severity: Low (latent API-contract divergence; self-healing today)
Verification: analysis-only (skipped per contract β Low severity, kernel-
internal, no deterministic in-tree multi-waiter trigger; the file's other
defects dominate observable behavior)
Analysis
void
wake_up_bit(void *addr, int bit)
{
wakeup_one(addr);
}
The Linux contract (wake_up_bit) wakes all waiters waiting on
(addr, bit) β the wait identity is the (address, bit-number) pair via a
hashed wait table. This shim:
- ignores
bitentirely β the wake identity is just thewordpointer (the waiterstsleep(word, ...)at linux_wait.c:118). Any waiter on any bit of the word is a validwakeup_onetarget. - wakes exactly one waiter (
wakeup_one) regardless of how many waiters are queued on that word.
Consequences when two threads sleep on the same word (different bits, or
the same bit):
* the wake can be delivered to the wrong waiter (wrong-bit wake); the
true waiter is not woken by this call;
* with N waiters on one bit, Nβ1 waiters are not woken by a single
wake_up_bit β under Linux semantics each would be woken.
The wrong-waiter case does not currently escape as a spurious success:
the woken thread's do-while loop re-checks test_bit() and goes back to
sleep, so the observable effect is a stolen wakeup (added latency /
reliance on the caller's timeout), and the stranded waiter is
accidentally rescued by the 1 Hz polling that DF-5183 documents
(MAX_SCHEDULE_TIMEOUT truncation). In-tree usage keeps at most one
wait_on_bit* sleeper per word at a time: both i915 wait sites
(i915_irq.c:3199 HANDOFF and i915_irq.c:3341 RESET_ENGINE+n) execute
under the I915_RESET_BACKOFF serialization (i915_irq.c:3324-3331), so
the fanout defect is latent with current callers.
Reachable in the future by any driver that waits on two bits of one
unsigned long concurrently β a pattern the Linux API explicitly
supports and drm code (e.g. the i915 reset flags word) is shaped for.
Suggested fix
Encode the bit into the sleep identity or use an exclusive wakeup keyed per (word, bit), and wake all waiters of that pair:
--- a/sys/dev/drm/linux_wait.c
+++ b/sys/dev/drm/linux_wait.c
@@ -97,7 +97,8 @@ void
wake_up_bit(void *addr, int bit)
{
- wakeup_one(addr);
+ /* Linux semantics: wake all waiters on (addr, bit). */
+ wakeup((void *)((uintptr_t)addr ^ (bit + 1)));
}
with the matching tsleep() ident in wait_on_bit_timeout() (and
wait_on_bit()), e.g. tsleep((void *)((uintptr_t)word ^ (bit + 1)),
...). (XOR-folding the bit into the ident keeps per-bit wake identity;
wakeup() restores the all-waiters fanout. Collisions with unrelated
sleep idents are no worse than the current single-word ident.)
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
source analysis: wakeup_one single fanout (kern_synch.c:1180), ident carries no bit info
Verified recommended fix
encode bit into sleep identity; wakeup() instead of wakeup_one(addr)
Verdict
untested (analysis-only: Low, kernel-internal, no deterministic in-tree multi-waiter trigger)
No comments yet.