SLEEPQ_HASH misplaced mask causes massive OOB index into sleepq_chains array
Summary
SLEEPQ_HASH(:82-83) = ((wchan>>10) ^ (wchan & SLEEPQ_HMASK)) -- the & SLEEPQ_HMASK masks ONLY the second XOR operand, not the whole result. For typical kernel pointers, hash returns ~54-bit value, but sleepq_chains has only 1024 entries. SLEEPQ_LOOKUP(:85) = &sleepq_chains[huge] -> wild OOB pointer -> spin_lock on arbitrary address. IMMEDIATE panic/memory corruption on first call. Currently DEAD CODE (zero in-tree callers, compiled into kernel). Fires the instant Linux KPI compat is wired up.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0139 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| sleepq_hash_oob.c | trigger-source | standalone arithmetic harness replicating SLEEPQ_HASH, proves OOB index | 4.6 KB | view raw |
| build.sh | build-script | cc -Wall -O2 -o sleepq_hash_oob sleepq_hash_oob.c | 195 B | view raw |
| run.sh | run-script | ./sleepq_hash_oob | 115 B | view raw |
| build.log | build-log | final successful build | 13 B | view raw |
| run.log | run-log | decisive run, OOB indices for all 6 wchan samples | 1012 B | view raw |
| kernel_syms.txt | evidence | nm shows sleepq_* symbols present in /boot/kernel/kernel (compiled in) but zero callers | 253 B | view raw |
| env.txt | environment | uname + cc version | 188 B | view raw |
| fix.diff | suggested-fix | add outer & SLEEPQ_HMASK to the whole XOR result | 549 B | view raw |
| VERDICT.md | verdict | full narrative | 3.8 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0139 β SLEEPQ_HASH misplaced mask β massive OOB index into sleepq_chains
Verdict: REPRODUCED (code-defect confirmed; latent β dead code at runtime)
The macro defect is real and confirmed by code inspection and a standalone
arithmetic harness. The buggy code path is, however, dead code on the current
guest: subr_sleepqueue.c exports sleepq_lock()/sleepq_add()/etc. (symbols
present in /boot/kernel/kernel) but has zero in-tree callers β the file is
a FreeBSD/Linux-KPI compatibility shim awaiting a consumer. So there is no live
runtime trigger today; the defect fires the instant any caller is wired up
(which is the file's documented sole purpose: "ONLY USE THIS FOR FREEBSD
COMPATIBILITY, E.G. THE LINUX KPI").
The bug (sys/kern/subr_sleepqueue.c:82-83)
#define SLEEPQ_HSIZE 1024
#define SLEEPQ_HMASK (SLEEPQ_HSIZE - 1) /* 0x3FF, 10 bits */
#define SLEEPQ_HASH(wchan) ((((uintptr_t)(wchan) >> 10) ^ \
((uintptr_t)(wchan) & SLEEPQ_HMASK)))
#define SLEEPQ_LOOKUP(wchan) &sleepq_chains[SLEEPQ_HASH(wchan)]
The & SLEEPQ_HMASK masks only the second XOR operand (wchan & 0x3FF),
not the whole XOR result. wchan >> 10 for any kernel pointer (high virtual
address bits 0xffff_8...) yields a ~54-bit value; XOR-ing in 10 low bits
leaves all the high bits intact, so the returned hash is a 54-bit value while
sleepq_chains has only 1024 (10-bit) slots.
SLEEPQ_LOOKUP() then computes &sleepq_chains[huge] β a wild pointer
~hundreds of petabytes past the array β which sleepq_lock() (line 182)
immediately feeds to spin_lock(), and sleepq_wclookup() (line 120) feeds to
TAILQ_FOREACH. First call β instant panic or silent corruption of unrelated
kernel memory.
Evidence (harness)
sleepq_hash_oob.c replicates the macro byte-for-byte (uintptr_t == unsigned
long on the 64-bit kernel) and runs it over 6 realistic kernel wait-channel
pointers (kmem direct-map, kernel-text symbols, malloc'd structs, the global
array address itself). All 6 produce indices far outside [0,1023]:
0xffff800000000000 -> 0x003fffe000000000 *** OOB *** 0xffffffff80b34000 -> 0x003fffffffe02cd0 *** OOB *** 0xffff8000deadbeef -> 0x003fffe00037a980 *** OOB *** (array @ 0x401fc0) -> 0x00000000000013c7 *** OOB *** (even a low address overflows!)
SLEEPQ_LOOKUP(0xffff8000deadbeef) lands 576,456,354,373,644,288 bytes
past the array start. No live panic is possible because nothing calls the API.
Exploit chain
none β this is a latent code defect, not a memory-corruption primitive
reachable from userspace. There is no live caller, so there is no trigger, no
slab, no victim object, no uid=0 chain to develop. The realistic impact
ceiling if/when Linux KPI is wired up is immediate panic or arbitrary kernel
memory corruption on first sleepq use (Critical-grade), but on the current
default guest the impact is none (dead code).
Fix
fix.diff adds the missing outer mask so the whole XOR result is reduced to the
hash range:
#define SLEEPQ_HASH(wchan) (((((uintptr_t)(wchan) >> 10) ^ \
((uintptr_t)(wchan) & SLEEPQ_HMASK))) & SLEEPQ_HMASK)
git apply --check passes. This is a one-token (& SLEEPQ_HMASK) fix at the
root cause; no caller logic changes.
Fix validation
not_testable: there is no live caller to exercise on a booted guest, so a
before/after runtime comparison is impossible. The fix is validated by
git apply --check (clean) and by the harness showing the fixed macro
(SLEEPQ_HASH_FIXED in the source, column "fixed") returns in-range indices
(0x0, 0xd0, 0x180, β¦ all < 1024) for every sample. Building a patched
kernel would change no observable runtime behavior because nothing calls the API.
Fix verification
not_testablecompile+harness validated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
REPRODUCED (harness). SLEEPQ_HASH misplaced mask -> massive OOB index. Zero callers in tree (dead code).
No comments yet.