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

```c
#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:

```c
#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.
