Attach error-path asymmetry: double bus_dmamap_destroy (double kfree of M_DEVBUF maps) and callout_drain/bus_dmamap_destroy walked through a NULL hcb array
Summary
Two attach-failure defects: (a) on partial per-hcb DMA alloc failure the fail path destroys the maps it created, then the attach error cleanup destroys them AGAIN via free_all - bus_dmamap_destroy kfree()s with no idempotence = double free into M_DEVBUF/kmalloc zone; (b) if the hcb-array kmalloc fails, free_all still runs dma_free_per_hcb with hcb_cnt set before the failure and hcbs==NULL - the helper computes hcb = NULL + i and callout_drains on a wild pointer: panic during OOM recovery. Boot/attach-time heap corruption on pvscsi-equipped systems under memory pressure (driver in GENERIC). hcbs_setup-bookkeeping fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4223 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | readme | summary | 3.1 KB | β raw |
| VERDICT.md | verdict | call-graph trace of both defects + fix validation | 5.6 KB | β raw |
| fix.diff | suggested-fix | sc->hcbs_setup bookkeeping | 1.1 KB | view raw |
| patch_apply.log | apply-log | clean patch -p2 | 371 B | view raw |
| build.log | build-log | module rebuild with fix | 876 B | view raw |
| env.txt | environment | guest uname, source md5, no-VMware-PCI note | 478 B | view raw |
| verdict.json | verdict | machine verdict | 3.5 KB | view raw |
DF-4223 β pvscsi attach/detach error-path asymmetry: double bus_dmamap_destroy (double kfree) and wild callout_drain through a NULL hcb array
File: sys/dev/virtual/vmware/pvscsi/pvscsi.c (pass 2; DF-1454/DF-1455 excluded)
What
pvscsi_free_all() (pvscsi.c:1566-1608) assumes sc->hcb_cnt per-hcb DMA
resources exist and that sc->hcbs is valid. Both assumptions break on
attach-failure paths, producing two distinct memory-safety defects:
(a) Double bus_dmamap_destroy β double kfree
On partial failure of pvscsi_dma_alloc_per_hcb() (pvscsi.c:558-621):
- the fail path at pvscsi.c:615-618 runs
pvscsi_dma_free_per_hcb(sc, i)β destroying thebus_dmamap_ts it created for hcbs[0..i)(pvscsi.c:549-552); pvscsi_attach()then callspvscsi_free_all()(pvscsi.c:1729), which runspvscsi_dma_free_per_hcb(sc, sc->hcb_cnt)again (pvscsi.c:1586) β destroying the same[0..i)maps a second time.
DragonFly's bus_dmamap_destroy()
(sys/platform/pc64/x86_64/busdma_machdep.c:439-449) tolerates a NULL map
but performs kfree(map, M_DEVBUF) without clearing the caller's pointer
and without any freed-flag β the second destroy is a double free into the
M_DEVBUF/kmalloc zone (heap corruption). The [i..hcb_cnt) maps that were
never created are NULL and harmless, which is exactly why this asymmetry
survives casual review.
(b) sc->hcbs == NULL β wild callout_drain/bus_dmamap_destroy
If the hcb array allocation fails (pvscsi.c:1718-1724), attach still calls
pvscsi_free_all(), which unconditionally runs
pvscsi_dma_free_per_hcb(sc, sc->hcb_cnt) with
sc->hcb_cnt = adapter_queue_size (set at pvscsi.c:1718 before the
failing kmalloc) and sc->hcbs == NULL. The helper then computes
hcb = sc->hcbs + i (pvscsi.c:542, 550) β a NULL/wild pointer for every i β
and executes callout_drain(&hcb->callout) (pvscsi.c:543-544) on it β
NULL/low-address kernel access β panic during OOM recovery (the exact
moment the system is already fragile).
Both are classic error-path asymmetry bugs: the partial-cleanup function and the global-cleanup function both free the same resources, and the global cleanup trusts counts that were set before the failed allocation.
Reachability
Requires a pvscsi controller present (driver probes) and then (a) a
bus_dmamap_create failure mid-loop or (b) an M_NOWAIT kmalloc failure
(~26 KB for the default 256-hcb array) β i.e. severe memory pressure or
resource exhaustion at attach time. Severity Low: attach-time, OOM-gated,
DoS/heap-corruption only, but the driver ships in GENERIC
(sys/config/X86_64_GENERIC:147).
Reproduce / validation
No pvscsi device on the audit guest β the faults cannot be scheduled.
Source trace in VERDICT.md. fix.diff (track how many hcbs actually have
per-hcb resources in sc->hcbs_setup; use it in pvscsi_free_all;
zero it after the partial free) applies cleanly to the pristine guest kernel
source and the module rebuilds (build.log).
Files
VERDICT.md, fix.diff, patch_apply.log, build.log, env.txt,
manifest.json, verdict.json
DF-4223 VERDICT β source-certain attach-error-path double-free and NULL-array wild access; HW(vmm)-gated guest
Status: inconclusive (no pvscsi device on audit guest); defects certain at source level
Defect (a): double bus_dmamap_destroy = double kfree
Call graph on a pvscsi_dma_alloc_per_hcb() partial failure:
pvscsi_attach (pvscsi.c:1727)
-> pvscsi_dma_alloc_per_hcb (pvscsi.c:558)
loop: bus_dmamap_create(...) for hcbs [0..k) (pvscsi.c:583-592)
FAILS at hcb k (map create error)
fail:
pvscsi_dma_free_per_hcb(sc, k) (pvscsi.c:615-618)
-> bus_dmamap_destroy(buffer_dmat, hcb->dma_map) for [0..k) (pvscsi.c:549-552)
each destroy kfree()s the map (busdma_machdep.c:445)
return error
-> device_printf + pvscsi_free_all(sc) (pvscsi.c:1728-1730)
-> pvscsi_dma_free_per_hcb(sc, sc->hcb_cnt) (pvscsi.c:1586)
-> bus_dmamap_destroy(...) for [0..hcb_cnt) AGAIN
[0..k): SECOND kfree of the same M_DEVBUF pointers <-- double free
[k..hcb_cnt): NULL maps (bus_dma_tag_create failure path
sets *mapp = NULL, busdma_machdep.c:429-430;
destroy tolerates NULL, busdma_machdep.c:442) β harmless
bus_dmamap_destroy (sys/platform/pc64/x86_64/busdma_machdep.c:439-449)
has no idempotence: kfree(map, M_DEVBUF) with no pointer zeroing and no
freed marker. A second destroy of the same pointer is a textbook double free
(kmalloc-zone corruption β freelist poisoning class). Note the adjacent
dmat->map_count-- also underflows for the never-created maps (cosmetic,
map_count is size_t β wraps, no memory fault).
The partial-cleanup inside pvscsi_dma_alloc_per_hcb and the global cleanup
in pvscsi_free_all both free the same objects with no shared bookkeeping β
that is the root cause. (For contrast, pvscsi_dma_free IS idempotent: it
bzero()s the descriptor after freeing, pvscsi.c:461 β the sg/sense DMA
frees in the same paths are safe; only the map-destroy loop is not.)
Defect (b): hcbs == NULL walked as an array
pvscsi_attach (pvscsi.c:1718) sc->hcb_cnt = adapter_queue_size; (256 default)
pvscsi_attach (pvscsi.c:1719) sc->hcbs = kmalloc(hcb_cnt * sizeof(*hcbs),
M_NOWAIT | M_ZERO);
pvscsi_attach (pvscsi.c:1721) if (sc->hcbs == NULL) { ... pvscsi_free_all(sc); ... }
pvscsi_free_all (pvscsi.c:1586) pvscsi_dma_free_per_hcb(sc, sc->hcb_cnt);
pvscsi_dma_free_per_hcb (pvscsi.c:541-544)
for (i = 0; i < hcbs_allocated; ++i) {
hcb = sc->hcbs + i; /* NULL + i*sizeof(pvscsi_hcb) */
callout_drain(&hcb->callout); /* wild read/write at NULL+104*i β PANIC */
hcb_cnt was set before the failing allocation, so the loop runs 256
times over a NULL base; the very first callout_drain dereferences NULL
(kern_timeout.c:1047 _callout_cancel_or_stop touches cc->flags etc.).
A system that just failed a 26 KB allocation now takes a kernel page fault
in its error path.
Reachability
Driver must be present (pvscsi PCI id 15ad:07c0 β VMware). Then:
(a) needs bus_dmamap_create failure partway through the hcb loop
(busdma tag/map resource exhaustion); (b) needs the hcb-array kmalloc to
fail under memory pressure. Both are attach-time-only, OOM-gated β severity
Low (DoS / heap corruption during boot-time stress; not attacker-schedulable
beyond memory-pressure induction). Ships in GENERIC:
sys/config/X86_64_GENERIC:147, sys/conf/files:3087.
Not triggerable on the audit guest: no pvscsi device (no 15ad PCI ids in
pciconf -l). Verified by code trace only.
Fix
fix.diff adds sc->hcbs_setup ("number of hcbs whose per-hcb DMA
resources exist"):
- set to
hcb_cntonly after the full alloc loop succeeds (pvscsi.c:612β new line); - zeroed in the fail path after the partial free, so
pvscsi_free_allnever re-destroys the already-destroyed maps; pvscsi_free_allusessc->hcbs_setupinstead ofsc->hcb_cnt(pvscsi.c:1586) β withhcbs == NULL,hcbs_setup == 0and the loop body never runs (softc is zero-allocated by newbus).
Detach behaviour is unchanged (on a fully attached device
hcbs_setup == hcb_cnt).
Fix validation
- pristine guest source md5
d3cfdba6fe7b03d200afcfaec291f544== hostsys/copy (env.txt); patch -p2 < fix.diffapplies cleanly (patch_apply.log);- pvscsi module rebuilds with the fix (
build.log). - Runtime validation HW-gated (no device) β
fix_status: inconclusive.
Kernel references
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:558-621 (dma_alloc_per_hcb + fail path)
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:615-618 (partial free)
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:1566-1608 (free_all)
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:1586 (uses hcb_cnt unconditionally)
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:1718-1724 (hcb_cnt set before failing kmalloc)
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:529-556 (dma_free_per_hcb walks sc->hcbs + i)
- sys/platform/pc64/x86_64/busdma_machdep.c:439-449 (destroy kfree, no idempotence)
- sys/platform/pc64/x86_64/busdma_machdep.c:429-430 (failed create sets *mapp = NULL)
- sys/kern/kern_timeout.c:1047 (callout_drain dereferences the callout)
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
inconclusivefix.diff (sc->hcbs_setup bookkeeping) applies cleanly to the pristine guest kernel source (patch_apply.log) and the pvscsi module compiles (build.log). Runtime validation HW-gated on this guest.
patch_apply.log (clean apply), build.log (module rebuild OK)
Confirmed kernel references
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:558-621
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:615-618
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:1566-1608
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:1586
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:1718-1724
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:529-556
- sys/platform/pc64/x86_64/busdma_machdep.c:439-449
- sys/kern/kern_timeout.c:1047
Detail
Evidence (decisive lines)
VERDICT.md call graphs: double destroy pvscsi.c:615-618 + pvscsi.c:1586 vs busdma_machdep.c:442-446 (kfree, NULL-tolerant but not double-tolerant); NULL-array walk pvscsi.c:1718-1724 -> 1586 -> 541-544. patch_apply.log + build.log: fix.diff (sc->hcbs_setup bookkeeping) applies and compiles on pristine guest source.
PoC changes
no PoC source β device-gated, OOM-gated; evidence is the source trace plus on-guest fix build validation
Verified recommended fix
Track hcbs whose per-hcb DMA resources exist (sc->hcbs_setup): set it only on full success, zero it after the partial free, and make pvscsi_free_all use it instead of hcb_cnt so freed maps are never destroyed twice and a NULL hcb array is never walked
Verdict
Source-certain attach-error-path asymmetry in pvscsi: (a) after a partial pvscsi_dma_alloc_per_hcb failure, the fail path destroys the per-hcb bus_dmamaps (pvscsi.c:615-618 -> 549-552) and pvscsi_attach's error cleanup destroys them AGAIN via pvscsi_free_all (pvscsi.c:1586) β DragonFly bus_dmamap_destroy (busdma_machdep.c:439-449) kfree()s without idempotence, so this is a double free into M_DEVBUF (heap corruption); (b) when the hcb-array kmalloc fails, pvscsi_free_all still walks pvscsi_dma_free_per_hcb(sc, sc->hcb_cnt) with sc->hcbs == NULL and hcb_cnt set before the failure (pvscsi.c:1718-1724, 541-544), calling callout_drain on NULL+i*sizeof(hcb) => panic during OOM recovery. Attach-time, OOM/resource-exhaustion-gated; driver ships in GENERIC. Not triggerable on the audit guest (no pvscsi device), so impact recorded none; realistic ceiling is boot-time DoS/heap corruption on VMware systems under memory pressure.
No comments yet.