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

adw_intr walks device-fed 32-bit completion tokens (areq_ba/next_ba) through unchecked acbbotov/carrierbotov pointer arithmetic β€” OOB kernel read/write and interrupt livelock

Summary

The RISC-written carrier fields are converted to acb/carrier pointers by raw base+offset macros with no bounds or alignment validation; a token outside the allocations yields wild kernel pointers that are then read from (ccb=acb->ccb, callout_stop) and written through (free_carrier->next_ba, adwfreeacb LIST_REMOVE/SLIST_INSERT_HEAD planting the forged acb on the free list). The unbounded while on the device-fed DONE bit additionally permits interrupt livelock. Buggy/compromised Advansys firmware or PCIe/DMA attacker: 32-bit-steerable (areq_ba, +4GiB) and 28-bit-steerable (next_ba) kernel memory corruption. Same threat class as the accepted narrow-sibling findings - the wide driver's CAM-layer instance missed in pass 1 behind the macro arithmetic. Validate-both-tokens fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4307 Β· 5 files
FileTypeDescriptionSize
VERDICT.md β€” 3.9 KB ↓ raw
README.md β€” 347 B ↓ raw
fix.diff β€” 1.7 KB view raw
manifest.json β€” 195 B view raw
verdict.json β€” 4.2 KB view raw

DF-4307 evidence pack

Source-level finding against sys/dev/disk/advansys/adwcam.c β€” hardware-gated (guest/QEMU has no Advansys HBA; pciconf shows no vendor 10cd). No runnable PoC exists without the device. See VERDICT.md for the full path:line proof and fix.diff for the validated-shape fix (patch -p1 apply-verified, never applied to sys/).

VERDICT.md
↓ download raw

DF-4307 β€” adw(4) unvalidated device-fed completion tokens β†’ OOB kernel pointers (adw_intr response-queue walk)

File: sys/dev/disk/advansys/adwcam.c β€” severity Medium (device/DMA-gated kernel memory corruption), bucket memcorrupt, confidence certain (source).

Root cause (path:line)

adw_intr() walks the RISC-produced response queue and converts two device-written 32-bit carrier fields directly into kernel pointers with zero validation:

  • sys/dev/disk/advansys/adwcam.c:1280 acb = acbbotov(adw, adw->responseq->areq_ba); acbbotov (adwlib.h:773-777) is literally (struct acb *)((caddr_t)adw->acbs + byteoffset) β€” a raw u32 added to the ACB array base. The legitimate token is the byte offset the driver placed in scsi_req_bo (adwlib.h:399-408 documents the firmware copies it back into areq_ba); nothing constrains the value the DMA memory actually contains at interrupt time.

  • sys/dev/disk/advansys/adwcam.c:1288-1289 adw->responseq = carrierbotov(adw, free_carrier->next_ba & ADW_NEXT_BA_MASK); carrierbotov (adwlib.h:800-804) = (carriers + byte_offset), no bounds check; the 28-bit masked device value can point up to ~256 MiB past the carrier array.

Consequences once either pointer goes wild:

  • adwcam.c:1290 free_carrier->next_ba = ... β€” 4-byte write through the unvalidated response-queue pointer (value = a small in-ring offset).
  • adwcam.c:1294-1295 ccb = acb->ccb; callout_stop(ccb->ccb_h.timeout_ch); β€” wild-pointer reads + callout manipulation through an acb up to +4 GiB from adw->acbs.
  • adwcam.c:1296-1332 β€” acb->queue field reads, bus_dmamap_unload on acb->dmamap, then adwfreeacb(adw, acb) (adwcam.c:128-144) which does LIST_REMOVE(&acb->ccb->ccb_h, ...) and SLIST_INSERT_HEAD(..., acb, ...) β€” writes through both wild pointers, planting the forged acb on the driver's free list for reuse.
  • adwcam.c:1264 loop condition reads responseq->next_ba on every iteration of an unbounded while β€” a device-fed self-referential DONE ring livelocks the interrupt handler (DoS), and each OOB hop keeps reading next_ba from whatever kernel memory it lands in.

Bounds that should hold (and don't): areq_ba < adw->num_acbs * sizeof(struct acb) (aligned); (next_ba & ADW_NEXT_BA_MASK) < (max_acbs + ADW_NUM_CARRIER_QUEUES + 1) * sizeof(struct adw_carrier) (16 bytes each, adwmcode.h:106-113).

Threat model

The token fields are produced by the Advansys RISC firmware / any PCI device able to DMA into the carrier rings: buggy firmware, a compromised HBA, or a Thunderbolt/PCIe attacker. The narrow-driver sibling of this exact class was already accepted (DF-2412/DF-2413/DF-2414 ccb_infos[] walk; DF-4151 free_q_head sense DMA). This is the wide driver's completion-queue instance, which pass 1 missed because it lives behind macro pointer arithmetic in the CAM-layer file.

Reproduction

Hardware-gated: requires an Advansys PCI HBA (ABP[3]940UW/950UW/3940U2W, PCI IDs 10cd:2300/2500/2700). The audit guest has no such device (pciconf -l | grep 10cd β†’ empty; QEMU has no Advansys emulation), and the RISC must be made to emit a bad token. Source-level certainty only β€” status untested. (Simple harnesses of acbbotov pointer arithmetic would be tautological; no meaningful harness exists without the device or a full bus_dma/CAM stub of adw_intr.)

Fix

fix.diff β€” validate both tokens against the allocations before conversion; on violation, log + adw_reset_bus() + abandon the walk (the rings are rebuilt by recovery). patch -p1 apply-verified.

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 against the read-only sys/ tree (patch -p1 apply-verified); cannot be runtime-tested without Advansys hardware.

findings/poc/DF-4307/fix.diff
↓ fix.diffper-fix-DF-4307

Confirmed kernel references

Detail

Exploit chain

Compromised/buggy Advansys HBA firmware (or PCIe DMA attacker) completes a carrier with areq_ba = u32 of its choice -> acb = adw->acbs + u32 -> OOB read of acb->ccb gives a near-arbitrary kernel pointer ccb -> callout_stop(ccb->ccb_h.timeout_ch) and adwfreeacb write through it (forged acb inserted on the driver free list for reuse) -> kernel memory corruption with a 4GiB-range, 32-bit-steerable offset from a known DMA-coherent allocation; device-fed next_ba gives a second, 28-bit-steerable OOB write. Device-gated: no unprivileged-software-only route.

Evidence (decisive lines)

findings/poc/DF-4307/VERDICT.md (path:line trace); the two unchecked inline conversions adwlib.h:773-777 and :800-804; sinks adwcam.c:1290/1294-1295/1332->133/142; guest hardware probe NO_ADVANNSYS in DF-4310 env session

PoC changes

No seed PoC (finding new in pass 2). No executable PoC possible without Advansys hardware; pack documents the source-level proof and the bounds-check fix.

Verified recommended fix

Validate areq_ba against num_acbs*sizeof(struct acb) (with alignment) and next_ba against the carrier allocation size in adw_intr before acbbotov/carrierbotov; on violation reset the bus and abandon the walk β€” see fix.diff.

Verdict

Source-certain, hardware-gated. adw_intr() converts two device-written 32-bit carrier fields into kernel pointers with no validation: acbbotov(adw, responseq->areq_ba) at adwcam.c:1280 (acbbotov = acbs + raw u32, adwlib.h:773-777) yields the 'struct acb ' that is then read (ccb = acb->ccb, queue fields), passed to callout_stop (adwcam.c:1295) and WRITTEN via adwfreeacb's LIST_REMOVE/SLIST_INSERT_HEAD (adwcam.c:133/142); carrierbotov(free_carrier->next_ba & ADW_NEXT_BA_MASK) at adwcam.c:1288-1289 (adwlib.h:800-804) advances responseq up to ~256MiB past the carrier array and adwcam.c:1290 writes free_carrier->next_ba through it (4-byte OOB write). The unbounded while at adwcam.c:1264 on the device-fed DONE bit additionally permits interrupt-context livelock. Trigger requires the Advansys RISC (buggy firmware, compromised HBA, or PCIe/DMA attacker) to emit a token outside [0, num_acbssizeof(struct acb)) / outside the carrier allocation β€” the exact threat class of accepted sibling findings DF-2412/2413/2414 (narrow driver ccb_infos walk) and DF-4151 (free_q_head sense DMA), here in the wide driver's CAM-layer completion path, which pass 1 missed behind the macro pointer arithmetic. No runtime reproduction possible: the audit guest has no Advansys PCI device (pciconf -l has no 10cd vendor; QEMU has no Advansys emulation) and the primitive needs device-issued DMA values; a userspace harness of the macros would be tautological. Bounds-check fix authored (fix.diff, patch-apply verified).