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

OOB array access in FBSS saved-commands reissue loop (hardcoded 32 vs sc_ncmds)

Summary

ahci_issue_saved_commands() FBSS branch at ahci.c:3967-3987: loop for(i=0;i<32;++i) hardcoded 32, not sc_ncmds(4-32). ci_saved from hardware CI/SACT at ahci.c:3890-3891 via timeout path with NO KKASSERT validation (unlike main intr path at :2622). ccb_at=ap->ap_ccbs[i].ccb_xa.at OOB if i>=sc_ncmds. Then ccb_at->at_target derefs garbage ptr. Requires AP_F_FBSS_ENABLED (CAP.FBSS or force_fbss tunable). Malicious controller stalls cmd until timeout then returns CI/SACT with bits beyond sc_ncmds. Fix: mask ci_saved to valid slot range before loop.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1212 Β· 9 files
FileTypeDescriptionSize
VERDICT.md verdict full narrative + path:line trace 4.0 KB ↓ raw
README.md readme reproduce / preconditions 1.4 KB ↓ raw
fix.diff suggested-fix mask ci_saved to (1<<sc_ncmds)-1 in FBSS branch 434 B view raw
build.sh build-log nativekernel build validation 379 B view raw
run.sh run-log no runtime trigger; static trace 450 B view raw
env.txt environment guest uname, PCI devices, GENERIC config 587 B view raw
build.log build-log kernel build log excerpt proving -Werror clean compile of patched source 1.1 KB view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme reproduce / preconditions
↓ download raw

DF-1212 β€” ahci FBSS saved-commands OOB array access

Reproduce

Not runnable on the audit guest β€” no AHCI controller is attached (only PIIX4 IDE + virtio-blk). The verification is a static source trace + a compiling fix.diff; see VERDICT.md for the line-by-line trace.

To validate the fix compiles, after applying fix.diff to /usr/src:

cd /usr/src
make -j6 nativekernel KERNCONF=X86_64_GENERIC    # ~6-8 min from warm obj

The kernel must link cleanly (=== NK_DONE rc=0 ===).

Bug location

sys/dev/disk/ahci/ahci.c:3972 β€” hardcoded loop for (i = 0; i < 32; ++i) in ahci_issue_saved_commands() FBSS branch, while ap->ap_ccbs is sized only sc->sc_ncmds (4..32). Out-of-range ci_saved bits from hardware CI/SACT registers cause ap->ap_ccbs[i].ccb_xa.at to be read OOB.

Trigger preconditions (NOT met on this guest)

  • An AHCI controller that supports FIS-Based Switching Saved (FBSS) β€” AP_F_FBSS_ENABLED.
  • The controller reports CAP.NCS < 31 (so the array is smaller than 32).
  • A malicious or buggy controller that stalls a command to timeout and then returns CI/SACT bits beyond the reported slot count.

Files

  • VERDICT.md β€” full narrative with path:line citations.
  • fix.diff β€” git-apply-able unified diff masking ci_saved to valid slots.
  • env.txt β€” guest environment (uname, PCI devices, kernel config).
  • build.log β€” relevant excerpt of the single-fix kernel build.
VERDICT.md verdict full narrative + path:line trace
↓ download raw

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:

} 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.

Fix verification

not_testable

compile validated

kernel/module build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. ahci FBSS ci_saved no mask vs sc_ncmds -> OOB ap_ccbs read. ahci in GENERIC, no AHCI HW.