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

nm_kr_rxpos() single conditional subtract turns DF-0359's corrupted nr_hwcur/nr_hwavail into an out-of-bounds slot write in netmap_transmit()

Summary

netmap_rxsync_from_host() guards the user-writable ring->reserved with 'resvd + ring->avail >= lim + 1' - a u32 sum that wraps: reserved=0xffffffff with avail=1 passes. The follow-on arithmetic then stores nr_hwcur up to 2*num_slots (outside the ring) and underflows nr_hwavail (that state is DF-0359). The consumer netmap_transmit() computes its insert index via nm_kr_rxpos() which performs only ONE conditional subtract - insufficient for the corrupted state - so slot = &ring->slot[i] indexes past the slot array and slot->len/slot->flags are written OOB. VERIFIED in a real kernel with verbatim functions in a KLD over a marker pool: 4,194,302 lie-cycles drove hwcur=2048/hwavail=0 and a 60-byte transmit wrote at slot[1024], one slot past the 1024-slot ring; fixed variant (rxpos modulo) wrote slot[0] in-range. Confined to the shared netmap pool (RW-mapped to clients anyway) - security meaning is cross-client corruption in the GLOBAL allocator (chains into DF-0547-class buf aliasing). Wheel-group gated; subsystem unbuildable on this tree. Fix: overflow-proof the guard (resvd>lim || avail>lim) + full modulo in nm_kr_rxpos.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3333 Β· 11 files
FileTypeDescriptionSize
df3333_kmod.c trigger-source KLD with VERBATIM rxsync_from_host + transmit insert block + nm_kr_rxpos over a marker pool; -DDD3333_FIXED applies rxpos modulo 9.2 KB view raw
Makefile build KMOD build 229 B ↓ download
build.sh build-script builds vulnerable + fixed variants 128 B view raw
build.log build-log full cc output, successful 5.9 KB view raw
run.sh run-script vulnerable|fixed 108 B view raw
run.log run-log vulnerable: OOB WRITE CONFIRMED at slot[1024] 491 B view raw
run.fixed.log run-log fixed: pos=0, no write past ring end 411 B view raw
dmesg.txt dmesg both decisive runs 596 B view raw
env.txt env guest env + subsystem unavailability proof 1011 B view raw
fix.diff suggested-fix overflow-proof resvd guard (netmap.c:910) + nm_kr_rxpos modulo (netmap_kern.h) 922 B view raw
VERDICT.md verdict full narrative incl. confinement analysis 4.3 KB ↓ raw
VERDICT.md verdict full narrative incl. confinement analysis
↓ download raw

DF-3333 β€” VERDICT

Finding: escalation of the DF-0359 ring-state corruption into an out-of-bounds slot access and OOB write in netmap_transmit().

Reproduced? YES β€” in a real kernel, with the verbatim functions.

Constraint (same as DF-3332): the netmap subsystem does not compile on this DragonFly tree (netmap_kern.h:747 / ifnet.if_unused7 removed; no sys/conf/files entry; no netmap.ko). The two vulnerable functions β€” netmap_rxsync_from_host() (netmap.c:884-929) and the netmap_transmit() insert block (netmap.c:2011-2044) β€” plus nm_kr_rxpos() (netmap_kern.h:540-556) depend on none of the rotted machinery, so they run verbatim in a KLD (df3333_kmod.c, stubs documented in-file; the only additions are marked INSTRUMENTATION) against a fake pool of [ ring | 1024 real slots | 1024 marker slots ].

Run 1 β€” vulnerable variant (dmesg.txt / run.log)

The choreography replays, ~4.19M times, exactly what an mmap()ed userspace thread can write before each NIOCRXSYNC on the host ring:

ring->reserved = 0xffffffff;  ring->avail = 1;   /* resvd+avail wraps to 0,
                                                    passes the >= lim+1 guard
                                                    (netmap.c:910) */
ring->cur = 1023 (then 0 alternating);

Result (dmesg):

df3333: choreography start (target hwcur>lim with hwavail<lim)
df3333: after 4194302 pairs: hwcur=2048 hwavail=0 (0x0)  <-- hwcur OUT OF RANGE
df3333: transmit used slot index 1024 (lim = 1023)  <-- OUT OF BOUNDS
df3333: RESULT OOB WRITE CONFIRMED at slot[1024] (1 slots past end of ring)  [VULNERABLE]

The slot->len = 60; slot->flags = 1 writes of netmap.c:2037-2038 landed in the marker region β€” past the end of the slot array β€” proving the OOB write, exactly at the index derived in the analysis (the reachable sum hwcur+hwavail is ≑ 0 mod num_slots, so the first OOB index is 1024).

Run 2 β€” FIXED variant (-DDD3333_FIXED: second pos %= num_slots in

nm_kr_rxpos β€” the netmap_kern.h part of fix.diff) (run.fixed.log)

df3333: transmit used slot index 0 (lim = 1023)
df3333: RESULT no write past ring end (pos=0)  [FIXED]

(The ring corruption itself β€” hwcur=2048 β€” remains; that is DF-0359's bug, addressed by the netmap.c part of fix.diff, the overflow-proof resvd guard.)

Impact assessment (why Low)

  • The OOB write lands inside the shared netmap memory pool (the ring object is followed by other pool objects/slack; netmap_mem2.c packs rings into clusters). The whole pool is RW-mapped to the netmap clients, so on its own this write crosses no privilege boundary β€” the attacking user could write the same bytes directly through their own mapping.
  • The security meaning is cross-client corruption: the pool is the global allocator shared by all netmap fds system-wide (netmap_get_memory_locked β†’ &nm_mem), so client A (wheel) can corrupt client B's ring header/slot/buffer state β€” including buffer indices (buf_idx values copied by netmap_sw_to_nic / read by other clients' syncs), which chains into DF-0547's cross-adapter double-free/aliasing.
  • Practically gated by /dev/netmap permissions (0660 root:wheel on this vintage) and β€” on this tree β€” by the subsystem not compiling at all.

Exploit chain (steps, not carried to uid0)

  1. NIOCREGIF host ring + mmap (wheel group);
  2. NIOCRXSYNCΓ—~8.4M with reserved=0xffffffff/avail=1 lie β†’ hwcur=2048, hwavail=0 (underflowed from DF-0359's arithmetic);
  3. host stack sends any packet via the interface β†’ netmap_transmit writes len/flags at slot[1024] β€” one slot past the victim ring.

Kernel references

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

Baseline: OOB write at slot[1024]. Patched (rxpos modulo part of fix.diff): in-range slot[0], no write past the ring. A/B in the same stock kernel via KLD; the resvd-guard part of fix.diff addresses DF-0359's root and was validated by arithmetic in VERDICT.md.

run.fixed.log; dmesg.txt
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #0 (same; verbatim-function A/B in KLD, full netmap kernel build impossible on this tree)

Confirmed kernel references

Detail

Exploit chain

NIOCREGIF host ring + mmap -> NIOCRXSYNC choreography with reserved=0xffffffff/avail=1 (guard bypass) drives hwcur out of range and hwavail to 0 -> any host-stack packet through the interface hits netmap_transmit -> slot->len/flags written one slot past the ring object into adjacent global-pool storage (another client's ring)

Evidence (decisive lines)

['run.log: hwcur=2048 hwavail=0 OUT OF RANGE -> transmit used slot 1024 (lim 1023) -> OOB WRITE CONFIRMED at slot[1024]', 'run.fixed.log: [FIXED] pos=0, no write past ring end', 'dmesg.txt: both decisive runs', 'VERDICT.md: confinement analysis (why the write stays in the shared pool)']

PoC changes

no seed; KLD with verbatim netmap_rxsync_from_host + netmap_transmit insert block + nm_kr_rxpos over [ring|1024 slots|1024 marker slots]; -DDD3333_FIXED applies the rxpos modulo from fix.diff

Verified recommended fix

make the resvd/avail guard overflow-proof (reject resvd > lim || avail > lim at netmap.c:910) and give nm_kr_rxpos a full modulo (pos %= nkr_num_slots) so corrupted ring state can never index past the slot array

Verdict

Escalation of DF-0359's ring-state corruption into an OOB write: netmap_rxsync_from_host()'s guard 'resvd + ring->avail >= lim + 1' (netmap.c:910) wraps in u32 (reserved=0xffffffff + avail=1 passes), leaving nr_hwcur=2048 (outside the 1024-slot ring) with nr_hwavail underflowed to 0; nm_kr_rxpos() (netmap_kern.h:541-545) then performs only ONE conditional subtract on hwcur+hwavail, so netmap_transmit() (netmap.c:2034-2038) indexes ring->slot[1024] -- past the end of the slot array -- and writes slot->len/slot->flags out of bounds. Demonstrated in a real DragonFly 6.5-DEVELOPMENT kernel with the verbatim functions in a KLD over a marker pool: after 4,194,302 lie-cycles (the exact values an mmap()ed thread can write), a 60-byte transmit wrote len/flags at slot[1024], one slot past the ring; the -DDD3333_FIXED variant (rxpos modulo) wrote slot[0] in-range. Impact confined to the user-mapped shared netmap pool (other clients' ring state in the GLOBAL allocator -> cross-client corruption, chains into DF-0547-class aliasing), so no privilege boundary is crossed by the write itself; rated Low/likely, dos-flavored. Netmap subsystem itself does not compile on current DragonFly (bit-rot), same as DF-3332.