# DF-4502 — ahci_pm_softreset leaks the error-CCB reservation on final-retry second-FIS failure

## Verdict: INCONCLUSIVE at runtime (feature absent on guest); code defect CERTAIN by trace

- **status**: `inconclusive` — the audit QEMU guest has no AHCI controller
  (PCI: PIIX3 IDE + virtio only, see `env.txt`), and QEMU does not emulate
  SATA port multipliers, so the priming condition (a PM target failing the
  second SRST FIS twice in a row) cannot be created on any available
  emulation. The defect itself is a plain, mechanical code-path asymmetry
  that is fully provable by reading the source; nothing about it is
  speculative.
- **expected impact on real hardware**: kernel panic (KKASSERT, INVARIANTS
  kernels) or permanently wedged port I/O (non-INVARIANTS kernels) — a
  local DoS on machines with a port multiplier and a misbehaving/flaky
  target.

## Root cause (line-cited)

`ahci_pm_softreset()` (sys/dev/disk/ahci/ahci_pm.c:679) acquires the
reserved error CCB at **ahci_pm.c:719**:

```c
ccb = ahci_get_err_ccb(ap);
```

`ahci_get_err_ccb()` (ahci.c:3331) does the one-shot reservation:

```c
KKASSERT((ap->ap_flags & AP_F_ERR_CCB_RESERVED) == 0);   /* ahci.c:3352 */
ap->ap_flags |= AP_F_ERR_CCB_RESERVED;                   /* ahci.c:3353 */
/* save outstanding command state */                      /* ahci.c:3356-3358 */
ap->ap_active = ap->ap_active_cnt = ap->ap_sactive = 0;  /* ahci.c:3364  */
```

and only `ahci_put_err_ccb()` (ahci.c:3379) restores the saved state and
clears the flag (ahci.c:3407-3412).

The softreset has TWO failure sites:

1. **First FIS poll** (ahci_pm.c:752-767) — releases the CCB
   *unconditionally* before deciding to retry or give up:
   ```c
   ahci_put_err_ccb(ccb);              /* ahci_pm.c:756 — always */
   if (--count) { ... goto retry; }
   goto err;                           /* already released: OK */
   ```

2. **Second FIS poll** (ahci_pm.c:805-817) — releases the CCB *only when
   retrying*:
   ```c
   if (ahci_poll(ccb, 1000, ahci_ata_cmd_timeout) != ATA_S_COMPLETE) {
           kprintf("%s: Soft-reset(2) through PM failed, %s\n", ...);
           if (--count) {
                   ahci_os_sleep(1000);
                   ahci_put_err_ccb(ccb);      /* ahci_pm.c:811 — retry only */
                   ...
                   goto retry;
           }
           goto err;                           /* ahci_pm.c:816 — NOT released */
   }
   ```

and the common exit `err:` (ahci_pm.c:862-877) never calls
`ahci_put_err_ccb()`.

So when the second FIS fails on the **final** retry (count 2→1 on an
earlier failure, then 1→0 here; or the mirror sequence), the function
returns with:

- `AP_F_ERR_CCB_RESERVED` still set, forever;
- `ap->ap_active`, `ap->ap_sactive`, `ap->ap_active_cnt` stuck at 0
  (their saved values are never restored);
- `at->at_probe = ATA_PROBE_FAILED` (ahci_pm.c:876).

Compare the non-PM twin `ahci_port_softreset()` (ahci.c:1233), which does
it correctly: its `err:` label releases unconditionally
(ahci.c:1393-1394). The asymmetry inside ahci_pm.c between its own two
failure sites (756 vs 811/816) shows the second site is simply missing
the unconditional release.

## Failure cascade after the leak

While the flag is stuck:

- `ahci_issue_pending_commands()` refuses to issue anything
  (ahci.c:2319-2322, "DELAY CCB" spew) — the port stops doing I/O; queued
  requests hang until CAM timeouts.
- The **next** `ahci_get_err_ccb()` on the port trips
  `KKASSERT((ap->ap_flags & AP_F_ERR_CCB_RESERVED) == 0)`
  (ahci.c:3352) → `panic("assertion ... failed")` on INVARIANTS kernels
  (X86_64_GENERIC is INVARIANTS, sys/config/X86_64_GENERIC:56;
  KKASSERT panics per sys/sys/systm.h:98-101). On non-INVARIANTS kernels
  the KKASSERT is a no-op (systm.h:118) and the port silently re-saves
  its already-zeroed active state, permanently corrupting completion
  accounting.
- Re-entry sites that call `ahci_get_err_ccb()`:
  - `ahci_pm_port_probe()` ahci_pm.c:188 — reached from any port-level
    hard reset: hotplug event → `ahci_port_state_machine()` →
    `ahci_port_reset(ap, NULL, 1)` (ahci.c:818-819) → `ahci_port_hardreset`
    → `ahci_pm_port_probe` (ahci.c:1710);
  - `camcontrol rescan <bus>` / wildcard XPT_PATH_INQ →
    `ahci_port_state_machine()` (ahci_cam.c:920-923) — same chain;
  - `ahci_port_read_ncq_error()` ahci.c:3441 (NCQ error recovery);
  - `ahci_port_softreset()` ahci.c:1307 (any port error recovery).
- `ahci_put_err_ccb()` on a port with real CI bits after bookkeeping
  corruption also hard-panics at ahci.c:3393/3399 (plain `panic`, not
  KKASSERT).

## Trigger

- **Priming** (device-driven): a target behind the PM fails the second
  SRST FIS on two consecutive softreset attempts. The code explicitly
  plans for this ("Soft-reset(2) through PM failed, retrying/giving up").
- **Detonation**: any later port reset/probe/error recovery on that port
  (hotplug, `camcontrol rescan`, CAM timeout escalation).

`trigger_DF4502.c` implements the detonation half (64 × wildcard rescan).
It builds and runs on the guest (see build.log/run.log); the guest has no
ahci controller, so the ahci sink is absent — no panic is possible there
by construction.

## Why not reproduced (honest accounting)

- Guest PCI inventory contains no AHCI controller (class 0x010601) —
  atapci0 is PIIX3 IDE; ahci.ko has nothing to attach to.
- Even with QEMU `-device ahci` (possible via `QEMU_EXTRA` in vm.sh),
  QEMU has **no port-multiplier emulation**: `ahci_pm_port_probe()` would
  fail to detect a PM at target 15 and none of the PM target paths
  (`ahci_pm_softreset`, `ahci_pm_read/write` under FBSS) would ever run.
- The bug is therefore classified `inconclusive` (code-certain,
  runtime-untestable on available hardware), consistent with how this
  audit records hardware-gated storage findings.

## Fix validation

Not performable: requires a PM-capable ahci controller and a device that
fails the second SRST FIS deterministically; no such emulation exists.
`fix.diff` is authored against the read-only sys/ tree (never applied) —
it mirrors the first-FIS pattern (release before the count check) so the
CCB is released on every failure exit, matching ahci.c:1393-1394's
err-label discipline.

## References

- sys/dev/disk/ahci/ahci_pm.c:719,752-767,805-817,862-877
- sys/dev/disk/ahci/ahci.c:2319-2322,3331-3413 (esp. 3352-3353, 3364, 3407-3412)
- sys/dev/disk/ahci/ahci.c:1307,1710,3441 (re-entry sites)
- sys/dev/disk/ahci/ahci_cam.c:920-923 (wildcard PATH_INQ → state machine)
- sys/sys/systm.h:98-101,118 (KKASSERT panic/no-op)
- sys/config/X86_64_GENERIC:56 (INVARIANTS)

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
