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)
PoC verification
Evidence pack
findings/poc/DF-3333 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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_idxvalues 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)
- NIOCREGIF host ring + mmap (wheel group);
- NIOCRXSYNCΓ~8.4M with reserved=0xffffffff/avail=1 lie β hwcur=2048, hwavail=0 (underflowed from DF-0359's arithmetic);
- host stack sends any packet via the interface β netmap_transmit writes len/flags at slot[1024] β one slot past the victim ring.
Kernel references
- sys/net/netmap/netmap.c:909-914 β overflowable resvd+avail guard
- sys/net/netmap/netmap.c:916-921 β hwcur stored out of range, hwavail underflow
- sys/net/netmap/netmap_kern.h:541-545 β nm_kr_rxpos single subtract
- sys/net/netmap/netmap.c:2034-2038 β OOB slot index β OOB len/flags write
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
fixedBaseline: 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
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.
No comments yet.