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.
Recommended fix
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)
PoC verification
Evidence pack
findings/poc/DF-2853 · 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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)
- Private
RMAN_ARRAYmanages[0x0000,0x2FFF]. - Legit:
A = reserve(0, 0x1EFF, 0x1F00, align=1)→[0,0x1EFF];B = reserve(0x1FFF, 0x2FFF, 0x1001, align=1)→[0x1FFF,0x2FFF]. Free fragment:F=[0x1F00,0x1FFE]. E = reserve(0, 0xFFFF, count=1, RF_ALIGNMENT_LOG2(12)): -rstart = roundup(0x1F00, 0x1000) = 0x2000-rend = 0x1FFE-rend - rstart + 1 = 0x1FFE - 0x2000 + 1wraps 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]Abefore[0x1fff]A).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.- Guest stays alive — silent metadata corruption.
kldunloadlater releases everything andrman_finireturns 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; usedDECLARE_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.shreplicates 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
-routput panics the kernel module linker withlost base for relatab(link_elf_obj_reloc_local) — cost onevm.sh reset with-src. - Fake
device_tvalues had to become NULL after they crashed the unprivileged sysctl walk (see amplifier above).
Fix validation
fix.diffadds the FreeBSD-style guard after therendcomputation: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 andfix_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
- sys/kern/subr_rman.c:236-254 (scan + wrapping size check)
- sys/kern/subr_rman.c:285-327 (split mutates free fragment)
- sys/platform/pc64/x86_64/nexus.c:398, sys/dev/acpica/acpi.c:1156, sys/dev/pccard/cardbus/cardbus_cis.c:533-539 (input provenance)
- FreeBSD sys/kern/subr_rman.c
rman_reserve_resource_step(if (rstart > rend) continue;) — upstream guard DragonFly lacks.
Fix verification
fixedfix.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']
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.
No comments yet.