# DF-1212 — ahci FBSS saved-commands OOB array access

## Verdict
**INCONCLUSIVE (not_testable on this guest)** — bug confirmed in source; runtime
not triggerable on this guest (no AHCI controller). Fix authored, applied to
in-guest source, and compiled clean as part of a single-fix `nativekernel` build.

## Finding summary
`ahci_issue_saved_commands()` (sys/dev/disk/ahci/ahci.c) has an FBSS (FIS-Based
Switching Saved) branch whose reissue loop is hardcoded to `for (i = 0; i < 32; ++i)`
even though `ap->ap_ccbs` is allocated with only `sc->sc_ncmds` slots
(`sc_ncmds = AHCI_REG_CAP_NCS(cap)`, 4–32). The hardware-supplied `ci_saved`
mask comes straight from `AHCI_PREG_SACT` / `AHCI_PREG_CI` (read at
ahci.c:3890–3891 on the timeout path with NO `KKASSERT` validation, unlike the
main interrupt path at :2622). A malicious or buggy controller that stalls a
command until timeout, then returns CI/SACT with bits set beyond `sc_ncmds`,
causes `ap->ap_ccbs[i].ccb_xa.at` to be read OOB; the resulting `ccb_at` is
then dereferenced (`ccb_at->at_target`) — kernel memory disclosure / panic.

## Source confirmation (audited tree)
- ahci.c:370 — `ap->ap_ccbs = kmalloc(sizeof(struct ahci_ccb) * sc->sc_ncmds, …)` — array sized `sc_ncmds`.
- ahci.c:3890–3891 — `ci_saved` read directly from hardware CI/SACT registers (no bounds validation on this path).
- ahci.c:3960 — FBSS branch taken only when `AP_F_FBSS_ENABLED` is set.
- ahci.c:3972 — `for (i = 0; i < 32; ++i)` — HARDCODED 32.
- ahci.c:3975 — `ccb_at = ap->ap_ccbs[i].ccb_xa.at;` — OOB read when `i >= sc_ncmds`.
- ahci.c:3977 — `fis_target = ccb_at->at_target;` — derefs the garbage pointer.
- ahci_attach.c:349 — `sc->sc_ncmds = AHCI_REG_CAP_NCS(cap);` — value 4..32 from hardware.
- ahci.h:50 — `AHCI_REG_CAP_NCS(_r)` extracts the 5-bit NCS field.

Compare to the *main interrupt path* at ahci.c:2622, where the analogous slot
extraction is `KKASSERT`-guarded; the timeout/FBSS path skips that guard.

## Why not runtime-reproduced on this guest
The QEMU/KVM guest exposes one PIIX4 IDE controller (atapci, `chip=0x70108086`)
plus virtio-blk for storage. There is **no AHCI controller attached**, so the
`ahci` driver's `attach()` never runs, no `ahci_port`/`ap_ccbs` array is ever
allocated, and the FBSS code path is dead at runtime on this guest. The driver
*is* statically compiled into the GENERIC kernel (`device ahci` in
`sys/config/X86_64_GENERIC`), but it has no live softc.

Triggering the bug requires a real (or emulated) AHCI controller that
(a) reports `CAP.NCS < 31`, (b) supports / has `AP_F_FBSS_ENABLED`, and
(c) returns `CI`/`SACT` bits beyond the reported slot count on a timeout —
i.e. a malicious or faulty controller. This is a hardware-supplied-input
finding; it cannot be exercised on the audit guest.

## Fix (fix.diff)
Mask `ci_saved` to the valid slot range at the top of the FBSS branch:
```c
} else if (ci_saved != 0) {
    ci_saved &= (1U << ap->ap_sc->sc_ncmds) - 1;
    ...
```
This is minimal, targeted at the root cause (untrusted hardware-supplied slot
bits), and matches the finding's recommended approach. The non-FBSS branch is
unaffected (the KKASSERT there guards the same invariant for ordinary ports).

## Fix validation
- `git apply --check -p1` against read-only `sys/` — clean.
- Applied to in-guest `/usr/src`, built with `make -j6 nativekernel KERNCONF=X86_64_GENERIC` (together with DF-1214 and DF-1223) — compiled clean, `NK_DONE rc=0`. The fix line `ci_saved &= (1U << ap->ap_sc->sc_ncmds) - 1;` is present at the built source.
- `fix_status: not_testable` — the bug path is unreachable at runtime on this guest (no AHCI HW), so a before/after PoC run is not possible; the diff is validated to apply + compile and traced to close the code path.

## Run / reproduce
Not runnable on this guest — no AHCI hardware. To reproduce on a system with a
real AHCI controller that supports FBSS, one would need either a malicious
PCIe device or a fault-injection harness that returns CI bits beyond NCS on a
timeout. The static trace above is the verification.
