DragonFlyBSD Kernel Audit
← triage · dashboard
DF-2853

rman_reserve_resource alignment-roundup unsigned wrap grants resources outside the scanned free fragment — overlapping allocations with live owners and free-fragment inflation across neighbors

Field Value
ID DF-2853
Status new
Severity High
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE CWE-190 / CWE-682
File sys/kern/subr_rman.c
Lines 247-254 (split :285-327)
Area kern
Confidence certain
Discovered 2026-09-02
Pass 2 (GLM 5.3 second pass)
Bucket memcorrupt
Reported pending
Known CVE none
CVE match novel

Summary

In the first-fit scan, rstart is round-UP to the alignment boundary and rend clipped to the fragment; if roundup(rstart,align) overshoots rend, (rend - rstart + 1) at :254 underflows to ~ULONG_MAX and passes the >= count check for ANY count. The split logic then (a) returns [rstart, rstart+count−1] — a range belonging to the NEXT region in the TAILQ, typically another device's ALLOCATED resource or unmanaged space, and (b) mutates the free fragment s->r_end = rv->r_start − 1, inflating it across its neighbor so subsequent plain reservations are granted inside an allocated region (double allocation). TAILQ sort order is also destroyed. FreeBSD guards exactly this with if (rstart > rend) continue; — DragonFly lacks the guard.

Threat model & preconditions

Kernel resource-metadata corruption with driver/firmware-reachable inputs: nexus hints, ACPI _CRS, and cardbus CIS where a physically-present malicious CardBus card's BAR decode chooses BOTH count and alignment. Any fragmented rman + alignment-seeking request can hand two drivers the same MMIO/IRQ range (cross-device register access, device-dependent privilege impact). Demonstrated amplifier: an unpriv hw.bus.rman reader traversing the corrupted list faulted in device_get_name — stale r_dev in real corruption = user-triggerable panic. No direct unpriv→uid0 chain from rman alone (no user-reachable parameter input).

Proof of contest

VERIFIED twice from fresh resets (findings/poc/DF-2853/rman_overlap.ko): E=[0x2000,0x2000] carved from inside allocated B=[0x1FFF,0x2FFF], then X=[0x1FFF] double-allocated with B — silent, list invariants gone. Fix (the FreeBSD-style guard) validated on rebuilt kernel #1: both requests NULL, list pristine.

Validated fix.diff in findings/poc/DF-2853/ (one guard).

Timeline

  • 2026-09-02 Discovered during pass-2 audit of subr_rman.c (GLM 5.3); silent overlap reproduced + fix validated same run.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2853 · 14 files
FileTypeDescriptionSize
rman_overlap.c 5.0 KB view raw
Makefile 316 B ↓ download
build.sh 879 B view raw
run.sh 321 B view raw
run.log 765 B view raw
dmesg.txt 726 B view raw
env.txt 536 B view raw
panic-sysctl-walk.txt 739 B view raw
fix.diff 541 B view raw
VERDICT.md 4.9 KB ↓ raw
verdict.json 5.4 KB view raw
manifest.json 1.1 KB view raw
fix_build.log 1.2 KB view raw
fix_run.log 527 B view raw
VERDICT.md
↓ download raw

DF-2853 VERDICT — REPRODUCED (baseline) / fixed by guard (validated)

Bottom line

On the stock X86_64_GENERIC INVARIANTS kernel (DragonFly 6.5-DEVELOPMENT

0 Thu Jul 2 06:02:54 UTC 2026), the KLD harness rman_overlap.ko

demonstrates in one load, without any kernel debug option, that rman_reserve_resource() hands out an address range inside another device's allocated resource and then double-allocates a unit, purely because the alignment roundup overflows the candidate fragment and the (rend - rstart + 1) >= count size check wraps unsigned.

Reproduced — exact sequence (run.log, dmesg.txt)

  1. Private RMAN_ARRAY manages [0x0000,0x2FFF].
  2. Legit: A = reserve(0, 0x1EFF, 0x1F00, align=1)[0,0x1EFF]; B = reserve(0x1FFF, 0x2FFF, 0x1001, align=1)[0x1FFF,0x2FFF]. Free fragment: F=[0x1F00,0x1FFE].
  3. E = reserve(0, 0xFFFF, count=1, RF_ALIGNMENT_LOG2(12)): - rstart = roundup(0x1F00, 0x1000) = 0x2000 - rend = 0x1FFE - rend - rstart + 1 = 0x1FFE - 0x2000 + 1 wraps to 0xFFFF…FFFF → “size” check passes for count=1 - split-else branch: F->r_end = 0x1FFF (inflated across B), E=[0x2000,0x2000] inserted — E is inside allocated B. Console: EVIL1 E=… [0x2000,0x2000] ==> REPRODUCED. - TAILQ also loses sort order ([0x2000]A before [0x1fff]A).
  4. X = reserve(0x1FFF, 0x1FFF, 1, align=1) is granted [0x1FFF,0x1FFF] from the inflated fragment while B still owns 0x1FFF → EVIL2 … REPRODUCED: 0x1FFF now owned by B AND X.
  5. Guest stays alive — silent metadata corruption. kldunload later releases everything and rman_fini returns 0 (the corrupted list survives the release path, tracing DF-0089-family merge hazards).

Amplifier — unprivileged readers walk the corrupted list

On the first attempt the harness used fake device_t values; an unprivileged hw.bus.rman walker (sysctl_rman, subr_rman.c:705-710 calls device_get_name(res->r_dev) on every exported entry) faulted on them and panicked the kernel — see panic-sysctl-walk.txt (Fatal user address access … device_get_name … fault address 0x59). That crash is a harness artifact, but it proves the reach: any corrupted/freed entry with a stale r_dev is dereferenced by an unprivileged sysctl read ⇒ user-triggerable panic; and every corrupted entry (start/size) is exported to userland.

Exploit chain assessment (honest)

rman_reserve_resource inputs are kernel-supplied (bus code, drivers, firmware tables): nexus.c:398 (driver/hints), acpi.c:1156 (_CRS), cardbus_cis.c:533-539 where a physically present malicious CardBus card’s BAR decode chooses size, hence count AND alignment via rman_make_alignment_flags(size). The primitive is kernel metadata corruption → two owners for the same MMIO/IRQ range; no direct unpriv→root chain from rman alone (no user-reachable parameter input). Impact ceiling: cross-device register access (device-dependent privilege consequences), plus invariant destruction that poisons later merges/releases (with DF-0089, merges already ignore contiguity) and the unpriv sysctl walk (panic). Classified memcorrupt/High.

PoC changes vs a “textbook” harness (poc_changes)

  • No seed PoC existed (pass-2 original finding).
  • DEV_MODULE() does not exist in DragonFly; used DECLARE_MODULE(name, moduledata, SI_SUB_DRIVERS, SI_ORDER_MIDDLE).
  • <kmod.mk> make include path is not usable on this guest (no populated sys/modules tree) → build.sh replicates kmod.mk by hand (machine/cpu/machine_base/cpu_base symlinks, -nostdinc -DKLD_MODULE -mcmodel=kernel -mno-red-zone, ld.bfd -d -r).
  • ld.gold must not be used: its -r output panics the kernel module linker with lost base for relatab (link_elf_obj_reloc_local) — cost one vm.sh reset with-src.
  • Fake device_t values had to become NULL after they crashed the unprivileged sysctl walk (see amplifier above).

Fix validation

  • fix.diff adds the FreeBSD-style guard after the rend computation: if (rstart > rend) continue; (also skips the bogus DPRINTF args).
  • Applied to the guest’s /usr/src/sys/kern/subr_rman.c (together with DF-2854’s bzero fix), make -j6 nativekernel KERNCONF=X86_64_GENERIC, make installkernel, reboot — see fix section of verdict.json and fix_build.log/fix_run.log.
  • Patched-kernel expectation (met): EVIL1 → NULL (“not reproduced (guard present?)”), EVIL2 → NULL (“correctly denied”), list stays [0-0x1eff]A [0x1f00-0x1ffe]f [0x1fff-0x2fff]A, unload fini=0.

Kernel references

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff (rstart>rend guard) applied to guest /usr/src together with DF-2854's bzero fix; make -j6 nativekernel KERNCONF=X86_64_GENERIC RC=0; installkernel + reboot into kernel #1. Re-run of the exact PoC: EVIL1 returns NULL ('not reproduced (guard present?)'), EVIL2 NULL ('correctly denied'), resource list remains [0-0x1eff]A [0x1f00-0x1ffe]f [0x1fff-0x2fff]A, module unloads with fini=0. Baseline corruption is gone.

['findings/poc/DF-2853/fix_run.log (patched-kernel dmesg)', 'findings/poc/DF-2853/fix_build.log (build RC=0)', 'findings/poc/DF-2853/fix.diff']
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Wed Sep 2 12:02:45 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Exploit chain

KLD harness: manage [0,0x2FFF]; A=[0,0x1EFF], B=[0x1FFF,0x2FFF] legit -> free F=[0x1F00,0x1FFE]; E=reserve(0,0xFFFF,count=1,RF_ALIGNMENT_LOG2(12)) -> roundup(0x1F00,0x1000)=0x2000 > 0x1FFE -> wrap -> E=[0x2000] inside B; F inflated to [0x1F00,0x1FFF]; X=reserve(0x1FFF,0x1FFF,1) -> 0x1FFF owned by B AND X. Real-world trigger: fragmented rman + alignment-seeking request (e.g. rman_make_alignment_flags(size) from cardbus CIS BAR decode, acpi _CRS, driver requests) -> two devices own the same MMIO/IRQ range.

Evidence (decisive lines)

["run.log: 'EVIL1 E=... [0x2000,0x2000] ==> REPRODUCED: allocated inside allocated B=[0x1FFF,0x2FFF] and outside free fragment F=[0x1F00,0x1FFE]'", "run.log: 'EVIL2 X=... [0x1fff,0x1fff] ==> REPRODUCED: 0x1FFF now owned by B AND X (double allocation)'", "run.log list dumps: after-E '[0x2000-0x2000]A' precedes '[0x1fff-0x2fff]A' (sort order destroyed)", 'panic-sysctl-walk.txt: unprivileged hw.bus.rman reader faults in device_get_name(res->r_dev) while walking the corrupted list', "fix_run.log: patched kernel => 'EVIL1 E=0 ==> not reproduced (guard present?)', 'EVIL2 X=0 ==> correctly denied', list intact", 'fix_build.log: make -j6 nativekernel RC=0 (patched subr_rman.c compiled)']

PoC changes

No seed PoC (pass-2 original). DEV_MODULE does not exist in DragonFly -> DECLARE_MODULE(...,SI_SUB_DRIVERS,SI_ORDER_MIDDLE); kmod.mk include path unusable in guest -> manual machine/cpu symlink + cc -DKLD_MODULE -mcmodel=kernel -mno-red-zone + ld.bfd -d -r (ld.gold output panics link_elf_obj_reloc_local with 'lost base for relatab' - one reset consumed); fake device_t args -> NULL after they crashed the unpriv sysctl walk.

Verified recommended fix

Add 'if (rstart > rend) continue;' after computing rend in rman_reserve_resource's unshared scan (mirrors FreeBSD rman_reserve_resource_step), and validate count/window arithmetic (count==0, count-1>end, start+count-1 overflow - the latter also covers DF-0090 and the DF-2855 window-escape variant).

Verdict

REPRODUCED on stock X86_64_GENERIC (DF 6.5-DEVELOPMENT #0, INVARIANTS): a single KLD load shows rman_reserve_resource handing out [0x2000,0x2000] from INSIDE allocated B=[0x1FFF,0x2FFF] (alignment roundup 0x1F00->0x2000 overflows fragment end 0x1FFE; (rend-rstart+1) wraps unsigned and passes the size check for count=1), then inflating the free fragment across B and DOUBLE-ALLOCATING 0x1FFF to a second request. Silent metadata corruption: guest stays up, list loses sort order and non-overlap invariants, poisoning later merges (cf. DF-0089) and the unprivileged hw.bus.rman walk (panic amplifier with stale r_dev, demonstrated in panic-sysctl-walk.txt). No direct unpriv->root chain (rman inputs are kernel/firmware supplied; cardbus CIS BAR sizing is the hardware-attacker path), so impact recorded as dos with memcorrupt-bucket metadata-corruption primitive (overlapping MMIO/IRQ ownership). Fix (rstart>rend guard, as FreeBSD rman_reserve_resource_step already has) validated on a rebuilt kernel: both evil allocations correctly return NULL, list stays pristine, unload clean.