β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5184

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5184 Β· 2 files
FileTypeDescriptionSize
README.md file 2.9 KB ↓ raw
verdict.json file 1.1 KB view raw
README.md file
↓ download 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:

  1. ignores bit entirely β€” the wake identity is just the word pointer (the waiters tsleep(word, ...) at linux_wait.c:118). Any waiter on any bit of the word is a valid wakeup_one target.
  2. 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_testable
baseline no→ patch + rebuild →patched clean
per-fix-DF-5184

Confirmed 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)