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

sili_pm_softreset polls with sili_quick_timeout whose sole-active KKASSERT is violated by co-active PM-target commands β€” XPT_RESET_DEV enters softreset without draining the port

Summary

The quick_timeout ONCHIP branch asserts sole-active, but the softreset path has no exclusive-access drain (the hardreset path does). camcontrol reset of a PM target while another target's command is on-chip and the softreset FIS times out -> KKASSERT panic on INVARIANTS; non-INVARIANTS force-completes unrelated in-flight commands as TIMEOUT (spurious CAM aborts). Drain-before-poll + demote-assert fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4694 Β· 11 files
FileTypeDescriptionSize
README.md β€” 1.4 KB ↓ raw
VERDICT.md β€” 3.0 KB ↓ raw
trigger_sili_pm.c β€” 3.1 KB view raw
build.sh β€” 120 B view raw
run.sh β€” 143 B view raw
build.log β€” 106 B view raw
run.log β€” 1.1 KB view raw
env.txt β€” 664 B view raw
fix.diff β€” 1.1 KB view raw
manifest.json β€” 1.3 KB view raw
verdict.json β€” 2.6 KB view raw

DF-4694 β€” sili_pm: sili_pm_softreset polls with sili_quick_timeout, whose sole-active KKASSERT is violated when other PM-target commands are still on-chip

Build & run

sh build.sh          # cc -O2 -Wall -o trigger_sili_pm trigger_sili_pm.c
sh run.sh            # ./trigger_sili_pm 15  (camcontrol reset = XPT_RESET_DEV storm)

Expected output

  • On hardware (SiI HBA + PM, several targets active): camcontrol reset on a PM target issues XPT_RESET_DEV β†’ sili_cam.c:904-915 β†’ sili_port_reset(ap, atx, 0) β†’ sili_pm_softreset() under the port lock without draining other targets' on-chip commands. If the softreset FIS times out while any other command is still active, sili_quick_timeout() hits KKASSERT((ap->ap_active & ~ap->ap_expired) == (1 << ccb->ccb_slot)) (sili.c:2537-2538) β†’ kernel panic (INVARIANTS is enabled in X86_64_GENERIC:56). On non-INVARIANTS builds the assert is compiled out and the code clears only its own ap_active bit then calls sili_port_reinit(), force-completing the other in-flight commands as ATA_S_TIMEOUT β€” spurious aborts of unrelated I/O.
  • On the audit QEMU guest: trigger runs, camcontrol reset 0/1 succeeds benignly against the PIIX3 nata buses (run.log) β€” no sili HBA exists (env.txt); hardware gate, not a false positive.

Contrast: sili_pm_hardreset() drains with sili_exclusive_access() first (sili_pm.c:272); the softreset path has no equivalent.

VERDICT.md
↓ download raw

VERDICT β€” DF-4694

Status: inconclusive (hardware-gated) Β· code-path proven Β· expected impact: kernel panic (INVARIANTS KKASSERT) or spurious abort of unrelated in-flight I/O

Reproduced?

No β€” no sili(4) HBA / no PM emulation on the audit guest (env.txt). The trigger runs camcontrol reset (XPT_RESET_DEV) 24+ times against the buses that exist (ata0/ata1 under nata) β€” all benign (run.log); the defective sili_pm_softreset() is unreachable here. Code-path proof below.

Why the defect is real (trace)

  1. sili_pm_softreset() polls its softreset FIS with sili_quick_timeout and holds the err_ccb: sili_pm.c:445-450 β€” if (sili_poll(ccb, timeout, sili_quick_timeout) != ATA_S_COMPLETE). The comment says quick_timeout is used because the err_ccb is held.
  2. sili_quick_timeout()'s ONCHIP branch asserts sole activity: KKASSERT((ap->ap_active & ~ap->ap_expired) == (1 << ccb->ccb_slot)); β€” sili.c:2536-2544. The assert encodes the contract "nothing else on the chip"; on INVARIANTS kernels (X86_64_GENERIC:56, confirmed on the guest) a violation is an immediate panic.
  3. Callers of sili_pm_softreset() do not guarantee that contract: - XPT_RESET_DEV: sili_cam.c:904-915 takes sili_os_lock_port(ap) only β€” no drain of other PM targets' commands β€” then sili_port_reset(ap, atx, 0) β†’ sili.c:744-745 β†’ sili_pm_softreset(). - Port-thread state machine: sili.c:632 (NEED_SOFT_RESET step) β€” the preceding hardreset drained, but completions that arrived meanwhile can have queued/issued followers before this step runs. Contrast: sili_pm_hardreset() explicitly calls sili_exclusive_access() first (sili_pm.c:272) β€” the softreset path has no equivalent.
  4. Trigger: with β‰₯1 other PM-target command on-chip and the softreset FIS timing out (1000 ms for target 15 / 8000 ms otherwise, sili_pm.c:445 β€” easily exceeded behind a PM when the target is hung), the KKASSERT fires β†’ panic. On non-INVARIANTS builds the code proceeds to clear only its own ap_active bit and calls sili_port_reinit() (sili.c:2540-2543), which force-completes the other still-on-chip commands as ATA_S_TIMEOUT (sili.c:403-411) β€” spurious CAM aborts of unrelated I/O and a port reinit storm.

The trigger program drives exactly this entry point (camcontrol reset = XPT_RESET_DEV) with a concurrent dd saturation stream; on PM hardware with a hung target this is the deterministic panic path.

Impact ceiling

Kernel panic from an unprivileged-ish trigger (any local user can issue camcontrol reset on a device node they can open, or the kernel reaches the same path from error recovery); requires PM hardware and a timed timeout. Severity Medium.

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 validation not testable: the guest has no SiI 3124/3132 HBA and QEMU does not emulate the SiL3726 port multiplier, so neither baseline nor patched kernel can reach sili_pm_* at runtime. fix.diff is a line-accurate source-level fix (all referenced helpers declared in sili.h); code-reviewed only.

env.txt (PCI inventory, 0 sili dmesg lines); fix.diff
↓ fix.diffper-fix-DF-4694

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md steps 1-4; run.log shows the XPT_RESET_DEV driver running; env.txt the hardware gate; INVARIANTS confirmed in guest /usr/src/sys/config/X86_64_GENERIC:56.

PoC changes

No seed; trigger written fresh (camcontrol reset storm is the XPT_RESET_DEV driver).

Verified recommended fix

Drain co-active commands (sili_exclusive_access) before the softreset poll, mirroring sili_pm_hardreset; downgrade the KKASSERT.

Verdict

Code-trace proven; hardware-gated: sili_pm_softreset polls with sili_quick_timeout (sili_pm.c:450) whose ONCHIP branch asserts sole activity - KKASSERT((ap->ap_active & ~ap->ap_expired) == (1 << slot)) at sili.c:2537-2538 - but XPT_RESET_DEV enters sili_pm_softreset under the port lock only, without draining other PM targets' on-chip commands (sili_cam.c:904-915 -> sili.c:744-745); contrast sili_pm_hardreset which drains via sili_exclusive_access (sili_pm.c:272). A softreset FIS timeout (1000/8000 ms) with any co-active command panics INVARIANTS kernels (X86_64_GENERIC:56, confirmed on guest); non-INVARIANTS builds force-reinit and abort the unrelated in-flight commands as ATA_S_TIMEOUT (sili.c:2540-2543 -> 403-411). Guest trigger drove 24+ camcontrol resets against ata0/ata1 benignly - no sili bus exists (env.txt).