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

os_map_pci_bar(): unchecked bus_alloc_resource_any() failure β†’ rman_get_virtual(NULL) NULL-deref panic at hptrr device attach (DF-5039 twin)

Summary

The BAR alloc is never NULL-checked before rman_get_virtual ((r)->r_virtual macro) - an unassignable BAR (reads 0, no resource-list entry) faults at VA 0x40 in attach; hptrr is compiled into GENERIC so an affected RocketRAID card panics every boot. VERIFIED on guest at source+harness fidelity: verbatim (rename-only) copy called against a real PCI device through the real newbus/rman stack - Fatal trap 12 at 0x40 exactly as production; guard A/B-validated in the same boot. Hardware-gated DoS ceiling.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5082 Β· 13 files
FileTypeDescriptionSize
README.md β€” 1.1 KB ↓ raw
VERDICT.md β€” 4.9 KB ↓ raw
df5082_harness.c β€” 6.7 KB view raw
build.sh β€” 156 B view raw
run.sh β€” 178 B view raw
build.log β€” 840 B view raw
run.log β€” 204 B view raw
run.2.log β€” 694 B view raw
panic.txt β€” 2.4 KB view raw
env.txt β€” 259 B view raw
fix.diff β€” 909 B view raw
manifest.json β€” 1.3 KB view raw
verdict.json β€” 4.4 KB view raw

DF-5082 β€” os_map_pci_bar() unchecked bus_alloc_resource_any() β†’ rman_get_virtual(NULL) NULL-deref panic at attach

sys/dev/raid/hptrr/hptrr_os_bsd.c:101-104 β€” DF-5039 twin (hpt27xx).

Reproduce

  1. Guest up with kernel source: dfbsd-qemu/vm.sh up
  2. Push + build: scp -F dfbsd-qemu/config df5082_harness.c build.sh dfbsd:/root/ dfbsd-qemu/vm.sh run_root 'sh /root/build.sh'
  3. Load module, run the FIXED variant first, then the VERBATIM copy: dfbsd-qemu/vm.sh run_root 'kldload /root/df5082/df5082.ko; sysctl df5082.trigger=2; sysctl df5082.trigger=1'

Expected

  • trigger=2: console/dmesg prints DF5082-fixed: ... guarded, no panic; guest survives.
  • trigger=1: console shows Fatal trap 12: page fault while in kernel mode, fault virtual address = 0x40, Stopped at ... movq 0x40(%rax),%rsi; the ssh call times out (RC=124), guest wedged at db>.

0x40 is offsetof(struct resource, r_virtual) (sys/sys/rman.h:106) β€” the fault address proves the read is the inlined rman_get_virtual(NULL) from hptrr_os_bsd.c:104.

Full story: VERDICT.md. Fix: fix.diff (validated A/B in-harness).

VERDICT.md
↓ download raw

DF-5082 β€” os_map_pci_bar() unchecked bus_alloc_resource_any() failure β†’ rman_get_virtual(NULL) NULL-deref panic at device attach

sys/dev/raid/hptrr/hptrr_os_bsd.c:85-106 β€” twin of DF-5039 (hpt27xx_os_bsd.c).

Verdict: REPRODUCED (source+harness, on-guest) β€” impact: panic (probe/attach-time DoS, hardware-gated)

The defect

os_map_pci_bar() (hptrr_os_bsd.c:85-106) allocates a PCI BAR and never checks the allocation result:

hba->pcibar[index].res = bus_alloc_resource_any(hba->pcidev,           /* :101 */
        hba->pcibar[index].type, &hba->pcibar[index].rid, RF_ACTIVE);

hba->pcibar[index].base = (char *)rman_get_virtual(hba->pcibar[index].res) + offset;  /* :104 */

rman_get_virtual(r) is the macro ((r)->r_virtual) (sys/sys/rman.h:149); offsetof(struct resource, r_virtual) == 0x40 on amd64 (sys/sys/rman.h:100-114: r_link 16 + r_sharelink 8 + r_sharehead 8 + r_start 8 + r_end 8 + r_flags 4 + pad 4). When the BAR cannot be allocated (unassigned BAR, resource conflict with another driver), the result is a read of physical address 0x40 β†’ Fatal trap 12 with the machine dead in the hptrr attach path. hptrr is compiled into X86_64_GENERIC (sys/config/X86_64_GENERIC:120), so the panic happens at boot when a RocketRAID 17xx/2xxx card is present with an unallocatable BAR β€” a persistent fail-to-boot.

When bus_alloc_resource_any() fails on DragonFly, concretely: - unimplemented/unassigned BAR (register reads 0): pci_add_map() returns early without resource_list_add() (sys/bus/pci/pci.c:2525, the ln2size < 4 early return), so resource_list_alloc() takes its !rle β†’ return 0 path (sys/kern/subr_bus.c:2673); - BAR whose range is already reserved non-shareable: rman_reserve_resource() fails inside the same call chain. Both funnel NULL into hptrr_os_bsd.c:104.

Reproduction (DF-4764 fidelity class: verbatim copy + real kernel APIs)

No RocketRAID 2xxx hardware exists on the audit guest, so β€” per the accepted DF-4764/DF-4765 precedent β€” the trigger executes a verbatim copy of hptrr_os_bsd.c:85-106 (rename-only) inside a KLD, with the real HBA/pcibar types from <dev/raid/hptrr/os_bsd.h> (layout proven by CTASSERT), against a real PCI device on the real newbus / pci / rman stack: the first PCI child whose BAR register reads 0 (hostb0, rid 0x10) β€” the same !rle β†’ NULL failure shape production hits for an unassignable BAR.

Console (full capture in panic.txt):

DF5082: victim hostb0 rid 0x10 (BAR reads 0 -> bus_alloc_resource_any will fail)
DF5082: calling VERBATIM os_map_pci_bar ...
Fatal user address access from kernel mode from sysctl at ffffffff8260030e
Fatal trap 12: page fault while in kernel mode
fault virtual address   = 0x40
fault code      = supervisor read data, page not present
Stopped at      df5082_trigger+0x2de:   movq    0x40(%rax),%rsi
db>
  • fault VA 0x40 = offsetof(struct resource, r_virtual) β€” the signature of rman_get_virtual(NULL), exactly as predicted;
  • Stopped at ... movq 0x40(%rax),%rsi β€” the inlined macro deref;
  • ssh run returned RC=124 (timeout) and the guest was wedged at the DDB prompt β€” the panic table's "guest dies" class.

Fix validation (A/B, same boot, same victim/rid)

The same harness carries the fix.diff-guarded variant. Run order in one boot: df5082.trigger=2 (fixed) twice, then df5082.trigger=1 (verbatim):

  • fixed (run.log / run.2.log): DF5082-fixed: bus_alloc_resource_any(rid 0x10) returned NULL; guarded, no panic β€” guest survived both;
  • verbatim (panic.txt): Fatal trap 12 at VA 0x40.

The guarded variant survived the identical NULL allocation that killed the verbatim copy β€” baseline reproduced (1), patched reproduced (0) β†’ fix_status: fixed (in-harness A/B; a full kernel rebuild was not performed because the in-tree attach path cannot be exercised without HighPoint hardware β€” the guard itself is exercised exactly at the NULL).

Impact ceiling

Probe/attach-time kernel panic β†’ persistent boot failure on a machine whose RocketRAID card has an unallocatable BAR (unassigned BAR or I/O conflict). Not reachable by an unprivileged local user; the trigger is hardware presence. Severity Low, bucket base:dev β€” consistent with DF-5039. The write is a fixed near-NULL read (0x40); no exploitation path beyond denial of service.

Not re-reported here (pass-2 duplicate discipline)

  • Shutdown-window timer UAF for hptrr: already filed as DF-4394 (hptrr_osm_bsd.c hpt_shutdown_vbus); the arming site in this file is os_request_timer (hptrr_os_bsd.c:218-226).

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

fix.diff guard validated in-harness A/B: same boot, same victim (hostb0, rid 0x10), same NULL allocation - fixed variant printed 'guarded, no panic' twice and the guest survived (run.log, run.2.log); the verbatim copy then panicked at VA 0x40 (panic.txt). Full-kernel rebuild validation not performed: the in-tree attach path requires HighPoint hardware; the guard is exercised exactly at the NULL allocation.

run.2.log (fixed variant survives, dmesg capture), panic.txt (baseline panic), fix.diff (the exact guard applied to df5082_os_map_pci_bar_fixed)
↓ fix.diffper-fix-DF-5082

Confirmed kernel references

Detail

Evidence (decisive lines)

panic.txt: 'fault virtual address = 0x40' + 'Stopped at df5082_trigger+0x2de: movq 0x40(%rax),%rsi' (verbatim-copy panic); run.log + run.2.log: 'DF5082-fixed: bus_alloc_resource_any(rid 0x10) returned NULL; guarded, no panic' (fix A/B, same victim hostb0/rid 0x10); VERDICT.md: full trace hptrr_os_bsd.c:101->104, rman.h:149 macro, 0x40 offset derivation; build.log: clean KLD build with CTASSERT layout proofs

PoC changes

no seed; harness authored for this finding: verbatim copy of os_map_pci_bar renamed df5082_os_map_pci_bar_copy/fixed (static, avoids clash with the kernel-resident hptrr_os_map_pci_bar), real-header HBA via os_bsd.h (needed SRCS+= device_if.h bus_if.h pci_if.h and an empty opt_cam.h in the kmod Makefile), victim = first PCI child with a BAR reading 0

Verified recommended fix

NULL-check bus_alloc_resource_any() in os_map_pci_bar() and return 0 on failure; guard os_unmap_pci_bar() against res==NULL/base==0 entries (fix.diff)

Verdict

REPRODUCED at source+harness on the audit guest (DF-4764 fidelity class): a KLD carrying a VERBATIM copy of hptrr_os_bsd.c:85-106 (rename-only) with the REAL HBA/pcibar layout from (CTASSERT-proven) calls it against a real PCI device (hostb0, rid 0x10, BAR register reads 0) through the real newbus/pci/rman stack; bus_alloc_resource_any() returns NULL exactly as in production for an unassignable BAR (pci_add_map() adds no resource-list entry for a zero BAR, sys/bus/pci/pci.c:2525, so resource_list_alloc() takes its !rle->0 path, sys/kern/subr_bus.c:2673), and the verbatim copy dies with 'Fatal trap 12: page fault', fault virtual address 0x40 == offsetof(struct resource, r_virtual), 'Stopped at df5082_trigger+0x2de: movq 0x40(%rax),%rsi' - the inlined rman_get_virtual(NULL) of hptrr_os_bsd.c:104; ssh returned RC=124 and the guest sat at db>. In production the detonator is hptrr device attach (driver compiled into X86_64_GENERIC, sys/config/X86_64_GENERIC:120) on a RocketRAID 17xx/2xxx card whose BAR cannot be allocated (unassigned BAR / ioport-memory conflict) -> persistent boot panic; not reachable by an unprivileged local user. Fix guard validated A/B in the same boot: the fix.diff variant survived the identical NULL allocation twice ('guarded, no panic') before the verbatim run panicked. Impact ceiling: probe/attach DoS; the primitive is a fixed near-NULL read, no escalation path.