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

Hardware-fed CI/SACT consistency KKASSERT (and CI==0 asserts) panic on chipset misbehavior the driver documents and recovers from elsewhere

Summary

ahci_port_intr() KKASSERTs that the hardware CI/SACT snapshot is a subset of the software slot masks, and ahci_get_err_ccb()/ahci_put_err_ccb() assert/panic on hardware CI != 0. The driver's own comments and recovery code document that chipsets (AMD after IFS errors) leave stale CI/SACT bits after a port stop, and the timeout path even warns-and-continues for it - but the next port_intr (invoked by the timeout path itself after software masks were cleared) trips the assert: recoverable error becomes guaranteed kernel panic. KKASSERT is always active in production DFly kernels. SATA device/chipset error condition during command timeout on affected hardware -> kernel panic (DoS). Same trust model as DF-1210/1211 but panic-only impact.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3772 Β· 5 files
FileTypeDescriptionSize
README.md β€” 3.0 KB ↓ raw
VERDICT.md β€” 2.8 KB ↓ raw
env.txt β€” 551 B view raw
fix.diff β€” 1.7 KB view raw
verdict.json β€” 2.2 KB view raw

DF-3772 β€” KKASSERT on hardware-fed CI/SACT consistency panics on chipset misbehavior the driver itself anticipates

File: sys/dev/disk/ahci/ahci.c (pass 2, GLM 5.3)

What the bug is

ahci_port_intr() opens every invocation with:

ci_saved = ahci_pread(ap, AHCI_PREG_SACT or AHCI_PREG_CI);   /* ahci.c:2615/2618 */
KKASSERT(!(ap->ap_sactive && ap->ap_active));                /* ahci.c:2621 */
KKASSERT((ci_saved & (ap->ap_sactive | ap->ap_active)) == ci_saved); /* ahci.c:2622 */

The second assertion requires the hardware CI/SACT register to be a subset of the driver's software masks. DragonFly's own comments document that real chipsets violate exactly this invariant:

  • ahci.c:3117-3123: "If we are stopped the AHCI chipset is supposed to have cleared CI and SACT. Did it? If it didn't we try very hard to clear the fields… IFS errors on (at least) AMD chipsets create this confusion."
  • ahci.c:3933-3948 (timeout path): detects ahci_pactive() != 0 after a stop, prods the chip, and on failure only prints "chipset failed to clear active cmds" β€” i.e. the driver is designed to survive stale CI/SACT bits.
  • ahci.h:1117-1122 (port_stop warning): stale CI/SACT races are expected.

But the recovery code at ahci.c:3125-3139 runs after the assert. Concrete panic sequence (no other bug needed):

  1. A command times out; ahci_ata_cmd_timeout() (ahci.c:3892) stops the port.
  2. An affected chipset leaves the slot's CI bit set (the documented AMD-IFS behavior).
  3. Timeout processing clears the software mask (ahci.c:3902/3905), restarts the port (3930), notices ahci_pactive() != 0 (3940), prods, gives up with a kprintf (3943-3947).
  4. The very next ahci_port_intr() (invoked by the timeout path itself at ahci.c:3951) re-reads CI with the stale bit set while the software mask no longer has it β†’ KKASSERT at ahci.c:2622 fails β†’ kernel panic.

Related asserts with the same trust model on the same error paths: - ahci_get_err_ccb(): KKASSERT(ci == 0) (ahci.c:3351) on the hardware CI register read at 3346 β€” violated by the same stale-CI condition. - ahci_put_err_ccb(): unconditional panic("…but CI %08x != 0") (ahci.c:3398-3403).

Impact: a device/chipset error condition that the driver is otherwise able to recover from converts into a guaranteed kernel panic β€” denial of service. Same attacker model as DF-1210/DF-1211 (device-fed registers), but impact is panic, not OOB memory access β†’ Low.

Why not reproduced on the guest

No AHCI controller on the audit guest (see env.txt). The assert is only reachable through a real AHCI interrupt/error path. Source-level reasoning is line-accurate; verdict not_reproduced (device absent).

Reproduce on suitable hardware

Any AHCI chipset known to leave CI/SACT dirty across a port stop during IFS error recovery (driver comments call out AMD 880G/SB850-class parts), plus a command timeout (hung/errored device). Alternatively a QEMU ich9-ahci regression test that deasserts CI improperly.

VERDICT.md
↓ download raw

DF-3772 VERDICT β€” not_reproduced (no AHCI hardware on guest); panic path certain by source

Bottom line

ahci_port_intr() asserts that the hardware-fed CI/SACT register snapshot is a subset of the driver's software slot masks (ahci.c:2621-2622). The driver's own comments and recovery code (ahci.c:3117-3139, 3933-3948) document that real chipsets β€” specifically AMD parts after IFS errors β€” leave stale CI/SACT bits after a port stop, and the driver is written to survive that condition. The KKASSERT runs before any of that recovery on every interrupt, so the documented misbehavior is turned into a guaranteed panic() instead of the intended recovery. The same pattern exists as KKASSERT(ci == 0) on a hardware read in ahci_get_err_ccb() (ahci.c:3346-3351) and as an explicit panic("…but CI %08x != 0") in ahci_put_err_ccb() (ahci.c:3397-3403).

Chain (why the assert fires despite the "recovery")

  • ahci.c:3890-3907 β€” timeout: read CI/SACT, stop port, clear software masks for expired slots.
  • ahci.c:3930 β€” restart port.
  • ahci.c:3940-3948 β€” detect stale hardware bits (ahci_pactive() != 0), prod, then give up with a warning only ("what do we do now?" β€” ahci.c:3137).
  • ahci.c:3951 β€” call ahci_port_intr(ap, 0); at ahci.c:2615/2618 the stale bits are read into ci_saved; software masks were cleared at 3902/3905; KKASSERT at 2622 fails β†’ panic.

No memory corruption β€” the subsequent walks are properly masked by software bits (checked: ahci.c:3080 ci_masked = ci_saved & *active & ~ap->ap_expired, ahci.c:3152 ci_masked = ~ci_saved & *active). The defect is purely the assert's contradiction with the driver's own hardware-behavior assumptions. CWE-617 (reachable assertion) β†’ DoS only β†’ Low.

Not-reproduced rationale

The guest has zero AHCI hardware (env.txt: pciconf empty, virtio-blk root, ahci static in GENERIC). The assert lives on the AHCI interrupt path and cannot be entered. No amount of guest setup short of a different QEMU machine config (vm.sh is fixed to virtio-blk) can instantiate it.

Replace the hardware-subset KKASSERT at ahci.c:2622 with mask-and-warn (keep the software-only invariant assert at 2621), downgrade ahci_get_err_ccb()'s KKASSERT (ahci.c:3351) to a warning + CI register clear, and replace ahci_put_err_ccb()'s panic() (ahci.c:3398) with a kprintf + register clear. fix.diff generated from a scratch worktree and verified to apply cleanly against the pristine tree.

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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff authored from line-accurate analysis; no hardware to build/boot-test against.

fix.diff in this pack.
↓ fix.diffper-fix-DF-3772

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md step-by-step chain (ahci.c:3890-3951 -> 2622); driver's own stale-CI documentation at ahci.c:3117-3139; related hardware-fed asserts at ahci.c:3351 and panic at ahci.c:3398

PoC changes

No runtime PoC possible - no AHCI controller on the audit guest (virtio-blk only).

Verified recommended fix

Replace the CI/SACT subset KKASSERT with mask-and-warn (chipset-stale bits), downgrade get_err_ccb KKASSERT and put_err_ccb panic to warnings + register clear.

Verdict

ahci_port_intr() KKASSERTs that the hardware CI/SACT snapshot is a subset of the software slot masks (ahci.c:2621-2622), but the driver's own comments and recovery code (ahci.c:3117-3139, ahci.c:3933-3948) document that chipsets (AMD after IFS errors) leave stale CI/SACT bits after a port stop. In the timeout path the software masks are cleared at ahci.c:3902/3905, stale hardware bits are detected at 3940-3948 with only a warning, and the port_intr() call at ahci.c:3951 then trips the assert at 2622 - a recoverable error becomes a guaranteed kernel panic. Same trust model as DF-1210/DF-1211 but panic-only impact. Not reproduced: guest has no AHCI controller (env.txt).