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

adv_pci_attach leaks parent_dmat on the adv_init() and IRQ failure unwinds β€” init_level is bumped to the ccb_infos teardown tier after creating parent_dmat

Summary

init_level=1 is the ccb_infos tier, not the parent_dmat tier - adv_free never destroys the tag on those unwinds (the overrun paths destroy it manually for exactly this reason). VERBATIM-replay harness on guest: real adv_init fails at the halt check, unwind executes zero parent_dmat destroys - leak proven; patched replay clean. Unbounded across attach retries.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5042 Β· 10 files
FileTypeDescriptionSize
run.log run-log baseline both modes: parent=0 destroys, LEAK PROVEN markers 2.4 KB view raw
run.2.log run-log patched adv_pci.c replay: parent=1, unwind clean 2.2 KB view raw
build.log build-log pristine kld build (-Werror kernel flags) 8.4 KB view raw
fix_build.log build-log patched-adv_pci.c kld build 3.7 KB view raw
fix.diff suggested-fix git-apply-able fix (covers the DF-5043 IRQ half too) 1.1 KB view raw
wrapper.c poc-source harness + verbatim adv_pci.c include 11.2 KB view raw
advansys_shim.c poc-source verbatim advansys.c with destroy-counting rename 658 B view raw
fkdecl.h poc-source shim declarations 2.8 KB view raw
Makefile build kmod makefile 593 B ↓ download
VERDICT.md verdict full narrative 4.6 KB ↓ raw
VERDICT.md verdict full narrative
↓ download raw

DF-5042 β€” VERDICT

Verdict: REPRODUCED (runtime, verbatim driver code, no hardware required for the replay) Class: resource leak / improper error handling (CWE-404/755) Impact ceiling: none (kernel kmem leak per failed attach; unbounded across attach retries) Confidence: certain

Reproduction

The guest has no AdvanSys HBA (no vendor 0x10CD device; pciconf -lv), and adv_pci.c is not even built on X86_64_GENERIC (device adv0 at isa registers a pci/adv module id but no hardware ever probes). The finding is therefore hardware-gated for the natural trigger, but the failure unwinds themselves were replayed at runtime on the guest by compiling the four driver translation units verbatim into a kld and driving the real adv_pci_attach() with a scripted chip (see README.md). This is the same technique the DF-4964 pack used for sili.

Two scenarios, both observed in run.log (fresh clean-source guest, kernel 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 2026):

  • mode INITFAIL β€” the real adv_init() runs against the scripted chip (signature present, CHIP_STATUS never sets ADV_CSW_HALTED), fails exactly at advansys.c:794-798 ("adv0: Unable to halt adapter. Initializationfailed"), and the adv_pci.c:260-264 unwind executes. Observation: bus_dma_tag_destroy calls for the parent_dmat: 0 β€” adv_free(adv) ran switch(1) which enters at case 1 (kfree ccb_infos β€” NULL) and never reaches case 2 (destroy parent_dmat). *** DF-5042 LEAK PROVEN ***.

  • mode IRQFAIL β€” fk_adv_init scripts success so attach reaches the IRQ stage; bus_alloc_resource(SYS_RES_IRQ) succeeds (fake resource), bus_setup_intr is scripted to fail; the adv_pci.c:289-294 unwind executes. Observations: parent_dmat destroy 0 (DF-5042) and bus_release_resource for the IRQ resource 0 (DF-5043, filed separately); only the IOPORT resource is released.

Mechanism (source-confirmed)

  • adv_pci.c:184 creates adv->parent_dmat; adv_pci.c:202 then does adv->init_level++ β†’ init_level == 1.
  • adv_free() (advansys.c:747-776) is a fall-through switch where case 1 = kfree(ccb_infos) and case 2 = bus_dma_tag_destroy(parent_dmat); the tiers 2..6 belong to adv_attach()'s own creations (advansys.c:1298,1339,1352,1360,1368). With init_level == 1 the unwind starts at case 1 and skips case 2 β€” parent_dmat is never destroyed.
  • The two overrun-allocation failure paths already work around exactly this by destroying adv->parent_dmat manually (adv_pci.c:212, adv_pci.c:222) β€” proof of intent β€” but the adv_init()-failure path (adv_pci.c:260-264) and the IRQ-setup-failure path (adv_pci.c:289-294) lack the manual destroy.
  • Every failed attach therefore leaks one bus_dma_tag_t (plus, on the IRQ path, the active IRQ resource β€” DF-5043). device_doattach() (sys/kern/subr_bus.c:2115-2124) drops the driver on attach failure without running any detach, so nothing else ever releases the tag; hotplug/rescan retries accumulate the leak.

Fix validation (runtime A/B, no kernel rebuild needed)

fix.diff adds the missing explicit bus_dma_tag_destroy(adv->parent_dmat) (and, on the IRQ path, bus_release_resource(SYS_RES_IRQ) β€” the DF-5043 half) to both unwinds, mirroring the file's own overrun-path idiom. Rebuilding the kld against the patched adv_pci.c and replaying both modes (run.2.log):

DF5042: bus_dma_tag_destroy: parent=1 overrun=0
DF5042: bus_release_resource: IOPORT=1 IRQ=1
DF5042: unwind clean (fix effective?)

Baseline leaks β†’ patched clean, same driver code otherwise. fix.diff is git apply --check clean against the audited tree.

Gating precondition (natural trigger)

AdvanSys ASC1200A/1200B/3000 PCI SCSI HBA (vendor 0x10CD) or VFIO-assigned endpoint that probes, passes the signature check, and then fails adv_init()/bus_setup_intr() β€” e.g. a wedged/missing chip behind a decoding BAR, or IRQ exhaustion. Root-relevant only as availability/kmem hygiene; no escalation path (leaked tag contains no user data).

Exploit chain

Not applicable β€” bounded-impact kmem leak per failed attach; no corruption, no info disclosure. The leak primitive is a bus_dma_tag_t (+ embedded segment array) per failed attach, repeatable with device re-probe loops.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

Baseline replay (pristine adv_pci.c): parent_dmat destroys 0 in both modes, LEAK PROVEN markers printed. Patched replay (fix.diff applied to the adv_pci.c copy used by the kld, same shims, same scripted chip): bus_dma_tag_destroy parent=1 and IRQ release=1, 'unwind clean (fix effective?)' in both modes. No kernel rebuild needed for the A/B because the module carries the driver code under test.

run.2.log (patched INITFAIL: parent=1; patched IRQFAIL: parent=1, IOPORT=1 IRQ=1); fix_build.log (patched build); run.log (baseline leaks)
↓ fix.diffper-fix-DF-5042

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (baseline: 'adv0: Unable to halt adapter. Initializationfailed' -> 'bus_dma_tag_destroy: parent=0' -> '*** parent_dmat NEVER DESTROYED on the failure unwind ... DF-5042 LEAK PROVEN ***' in both modes); run.2.log (patched replay: 'bus_dma_tag_destroy: parent=1', 'unwind clean'); fix.diff (git apply --check clean); VERDICT.md

PoC changes

Rewrote the seeded concept into a verbatim-replay kld: adv_pci.c #included unmodified (externals renamed via #define placed after the shim definitions, because DFly make treats target-specific CFLAGS as a replacement for the kmod global flags); advansys.c compiled via advansys_shim.c with a single destroy-counting rename; real pci0 device_t, real DMA tags, real shared overrun allocation; chip scripted by planting the signature in a pmap_mapdev window and leaving CHIP_STATUS at 0 so the real adv_init() fails at advansys.c:794; adv_find_signature forced (its byte@0x01==0x25 and word@0x00==0x04C1 checks cannot both be satisfied by a passive RAM window); fk_adv_alloc serves a private softc instead of pci0's.

Verified recommended fix

In both failure unwinds, destroy parent_dmat explicitly before adv_free() (mirroring adv_pci.c:212/222): bus_dma_tag_destroy(adv->parent_dmat); and on the IRQ path also bus_release_resource(dev, SYS_RES_IRQ, irqrid, irqres) when bus_setup_intr failed - see fix.diff

Verdict

adv_pci_attach() bumps init_level to 1 (the ccb_infos teardown tier) after creating parent_dmat, so adv_free() at init_level 1 enters switch case 1 (kfree ccb_infos) and never reaches case 2 (bus_dma_tag_destroy(parent_dmat)). Reproduced at runtime on the guest by replaying the VERBATIM driver code (adv_pci.c/advansys.c/advlib.c/advmcode.c compiled unmodified into a kld, only bus/PCI externals routed to observation shims) against a scripted chip: the real adv_init() failed deterministically at its halt check ('Unable to halt adapter') and the real unwind at adv_pci.c:260-264 executed with zero parent_dmat destroys; the bus_setup_intr-failure unwind (adv_pci.c:289-294) likewise. The overrun-allocation paths (adv_pci.c:212,222) already destroy parent_dmat manually for exactly this reason; the two later paths lack it. Natural trigger is HW-gated (AdvanSys 0x10CD PCI HBA required; none on the guest), impact is a kmem leak (bus_dma_tag_t + segment array) per failed attach, unbounded across re-probe retries - no corruption, no disclosure. fix.diff (explicit bus_dma_tag_destroy on both unwinds) validated by runtime A/B on the same guest: baseline parent=0 destroys / patched parent=1, 'unwind clean'.