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

idad_dump reports fake success for controller-failed/rejected dump writes β€” ida_command discards the controller error status (DF-5027 pattern)

Summary

ida_done detects HARD_ERROR/CMD_REJECTED then throws the status away for IDA_COMMAND (the driver's own XXX at ida.c:358) - a controller-rejected crash-dump write reports success and savecore believes a corrupt/short dump. Per-qcb error + propagate fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5047 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.6 KB ↓ raw
VERDICT.md β€” 3.5 KB ↓ raw
env.txt β€” 1.0 KB view raw
fix.diff β€” 896 B view raw
verdict.json β€” 3.4 KB view raw
manifest.json β€” 1.0 KB view raw

DF-5047 β€” idad_dump reports fake success for controller-rejected/failed dump writes (DF-5027 pattern in IDA)

File: sys/dev/raid/ida/ida_disk.c (pass 2, GLM 5.3) Bucket: base:dev | Severity: Low | Pattern: DF-5027 (ips_disk fake-success dumps)

What the bug is

idad_dump() (sys/dev/raid/ida/ida_disk.c:191-211) hands each dump chunk to ida_command() and returns its error straight to the crash-dump machinery:

    if (ap->a_length > 0) {
        error = ida_command(drv->controller, CMD_WRITE, ap->a_virtual,
            ap->a_length, drv->drive, ap->a_offset / DEV_BSIZE, DMA_DATA_OUT);
    }

But ida_command() (sys/dev/raid/ida/ida.c:317-362) can only ever return EAGAIN (no QCB, ida.c:329-332) or ETIMEDOUT (5s wait expiry, ida.c:449-461). The controller's own status for the command is detected and then discarded:

  • ida_done() reads qcb->hwqcb->req.error and flags HARD_ERROR / CMD_REJECTED (ida.c:515-524), but for IDA_COMMAND requests it merely does wakeup(qcb) (ida.c:526-528) β€” the error goes nowhere.
  • ida_wait() returns 0 unconditionally once the qcb completes (ida.c:463-467 polling path; :449-453 interrupt path).
  • The author knew: /* XXX should have status returned here? */ (ida.c:358-359).

Net effect: a dump write that the controller rejected or hard-errored returns success to dumpsys, which keeps advancing dumplo and finally prints "Dump complete" (sys/platform/pc64/x86_64/dump_machdep.c:353-364). The saved crash dump silently lacks or mis-contains the affected pages β€” exactly the DF-5027 copperhead pattern, here on Compaq SMART/IDA.

Threat model / trigger

Requires (a) an IDA controller as dump device, (b) a kernel panic, and (c) a controller that fails/rejects the write (failing cache, cable pulled, array gone). Result: silently corrupt/empty core dump during incident response β€” an integrity failure of the forensic record, not memory unsafety.

PoC / verification status

Hardware-gated (no Compaq SMART/IDA PCI device on the audit guest β€” pciconf evidence in env.txt; the driver never attaches, no /dev/idad* dump device can be configured). Code trace is line-accurate; see VERDICT.md.

Fix

Record the controller status per-qcb in ida_done() and propagate it out of ida_command(). See fix.diff (ida.c + idavar.h).

Reproduce (on hardware)

# IDA box: configure dumpdev=/dev/idad0s0?, panic the box while the
# controller's write path is failing (pull battery/cache, fail the array):
#   sysctl debug.debugger=1 ... call panic from ddb
# expect: dumpsys prints "ok"/"Dump complete" even though writes failed;
# savecore later yields a corrupt/short dump.
VERDICT.md
↓ download raw

DF-5047 VERDICT β€” idad_dump fake success via discarded controller status

status: untested (hardware-gated) | impact: none demonstrated (crash-dump integrity) | confidence: likely

Reproduced?

No. The trigger needs a physical Compaq SMART/IDA controller configured as the dump device. Guest evidence (env.txt): pciconf -l shows no vendor 0x0e11 function, so ida_pci_probe never matches (ida_pci.c:193-210) and no idad device exists (ls /dev/idad* -> NO_IDAD_DEVS). Untested = hardware absent, not refuted.

Why the finding is real (trace)

  1. Dump chunk sizes are safe here: si_iosize_max = min(MAXPHYS, 64*1024) (ida_disk.c:281) -> di.maxiosize (sys/kern/subr_disk.c:934) -> cb_dumpdata clamps chunks to min(MAXPHYS, maxiosize) = 64KB (sys/platform/pc64/x86_64/dump_machdep.c:168-177), so hwqcb->req.bcount = howmany(len, DEV_BSIZE) = 128 fits the u16 field (ida.c:411) and bus_dmamap_load stays within the MAXBSIZE tag (ida.c:209-213). The math is fine; the status reporting is the bug.
  2. idad_dump clears IDA_INTERRUPTS and polls (ida_disk.c:202-208) -> ida_command -> ida_wait polling branch (ida.c:455-467): on completion it calls ida_done(ida, qcb) and returns 0. It never checks anything afterwards.
  3. ida_done computes error from req.error & HARD_ERROR|CMD_REJECTED (ida.c:515-524) and, for IDA_COMMAND (which dump writes are β€” flags DMA_DATA_OUT | IDA_COMMAND, ida.c:350), only wakeup(qcb) (ida.c:526-528). The error bit dies there.
  4. ida_command returns error = whatever ida_wait said = 0 (ida.c:355-361), with the author's own admission at :358-359 (/* XXX should have status returned here? */).
  5. idad_dump returns that 0 to dev_ddump (sys/kern/kern_device.c:423-446) -> cb_dumpdata treats it as success and advances (dump_machdep.c:188-192); the run ends with "Dump complete" (:363-364).

A CMD_WRITE rejected by the controller therefore produces a "successful" dump with missing/garbage pages β€” the same integrity failure class as DF-5027 (ips). Because the affected writes are crash-dump pages only (the normal data path goes through qcb->bio and sets B_ERROR correctly at ida.c:530-532), disk data written by user I/O is unaffected.

Why Low

  • Hardware-gated (1999-era Compaq arrays; QEMU cannot emulate one).
  • Only exercised during a panic with a failing controller.
  • Impact is loss of forensic evidence (silent bad dump), not memory corruption or privilege.

Fix assessment

fix.diff records qcb->error = EIO in ida_done before the command wakeup and propagates it from ida_command after ida_wait succeeds. For the dump path (polling, crit-held, single-CPU post-panic) this is race-free and complete. For the general interrupt-path there remains a narrow qcb-reuse race (qcb goes back on the free list at ida.c:536 before the waiter reads qcb->error) β€” noted in the diff comment; a full fix would hold the qcb until the status is consumed. fix_status=not_testable: no IDA hardware on the guest to build and validate a patched kernel.

Bottom line

Real DF-5027-family defect, code-certain (the driver itself marks the missing status return with XXX), untestable here solely for lack of the hardware.

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 (ida.c + idavar.h) stores qcb->error in ida_done before the command wakeup and returns it from ida_command when ida_wait succeeded β€” dump writes can now report EIO. Race-free on the polling path dumps use (crit-held, single CPU post-panic); a narrow qcb-reuse race remains on the general interrupt path (noted in-diff; full fix would hold the qcb until status is read). No hardware to build/boot a validated kernel.

fix.diff (git apply --check: APPLIES_CLEAN); hunks at ida.c ida_command post-wait and ida_done pre-wakeup, idavar.h struct ida_qcb
↓ fix.diffper-fix-DF-5047

Confirmed kernel references

Detail

Evidence (decisive lines)

env.txt (no 0x0e11 PCI function, no /dev/idad* dump device configurable); VERDICT.md step-by-step trace incl. dump_machdep.c chunking proof that sizes are safe; fix.diff (git apply --check clean)

PoC changes

No runnable PoC exists (hardware-gated: needs an IDA controller as dumpdev plus a failing write path). Pack carries the code trace + hardware-absence evidence; fix.diff validated with git apply --check.

Verified recommended fix

Record the controller status per-qcb in ida_done (qcb->error = EIO on HARD_ERROR/CMD_REJECTED) and propagate it out of ida_command after a successful wait.

Verdict

DF-5027-family fake-success dump: idad_dump (ida_disk.c:204-207) propagates ida_command's return, but ida_command can only return EAGAIN/ETIMEDOUT β€” the controller's HARD_ERROR/CMD_REJECTED status is detected in ida_done (ida.c:515-524) and then discarded for IDA_COMMAND requests (ida.c:526-528 only wakeup(qcb); ida_wait returns 0 at ida.c:466-467; the driver's own 'XXX should have status returned here?' at ida.c:358-359). A controller-rejected crash-dump write therefore reports success and dumpsys prints 'Dump complete' with missing/garbage pages. Untested solely because the guest has no IDA hardware; the dump chunk math itself is safe (si_iosize_max 64KB at ida_disk.c:281 clamps cb_dumpdata chunks, so req.bcount u16 never wraps and dmamap_load stays within the MAXBSIZE tag). Low: panic-time, hardware-failure-gated integrity loss of the crash dump, not memory unsafety; normal user I/O error path (ida.c:530-532) sets B_ERROR correctly.