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)
PoC verification
Evidence pack
findings/poc/DF-5042 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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_STATUSnever setsADV_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_destroycalls for the parent_dmat: 0 βadv_free(adv)ranswitch(1)which enters atcase 1(kfree ccb_infos β NULL) and never reachescase 2(destroy parent_dmat).*** DF-5042 LEAK PROVEN ***. -
mode IRQFAIL β
fk_adv_initscripts success so attach reaches the IRQ stage;bus_alloc_resource(SYS_RES_IRQ)succeeds (fake resource),bus_setup_intris scripted to fail; the adv_pci.c:289-294 unwind executes. Observations: parent_dmat destroy 0 (DF-5042) andbus_release_resourcefor 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 doesadv->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 toadv_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_dmatmanually (adv_pci.c:212, adv_pci.c:222) β proof of intent β but theadv_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
fixedBaseline 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)
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'.
No comments yet.