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

m/M packets accept unbounded length overflowing 400-byte static I/O buffers

Summary

m (read memory) and M (write memory) packets trust attacker-supplied length int parsed by hexToInt() with no upper bound no rejection of negative values. For m mem2hex() writes 2*length hex bytes plus NUL into 400-byte static remcomOutBuffer; any length>200 corrupts adjacent kernel BSS until get_char hits unmapped page. For M hex2mem() reads 2*length bytes from pointer inside 400-byte remcomInBuffer reading past its end into adjacent BSS and writing resulting bytes to attacker-chosen kernel address. Crafted $m0 7fffffff overflows several GB past remcomOutBuffer through adjacent BSS until fault. hexToInt no negative rejection. [VERIFIED pass2: reproduced against the real stock stub - m len 264 overwrites 129 bytes past remcomOutBuffer into remcomInBuffer, len 0x300 overwrites 1137 bytes across four BSS symbols, M reads past packet NUL and past the 400-byte array writing decoded bytes at chosen kernel addresses with exact predicted content, negative lengths accepted; BSS overwrite plus arbitrary-address write for the console holder; clamp fixes-validated both handlers]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2362 Β· 12 files
FileTypeDescriptionSize
poc2362.py β€” 8.0 KB view raw
analyze2362b.py β€” 5.2 KB view raw
rig236.py β€” 5.1 KB view raw
rsp.py β€” 6.1 KB view raw
fix.diff β€” 1.7 KB view raw
run.log β€” 40.3 KB view raw
run.raw.log β€” 119.8 KB view raw
run.fixed.log β€” 33.3 KB view raw
env.txt β€” 409 B view raw
exp_m_overflow.py β€” 4.3 KB view raw
exp_m_oobread.py β€” 5.8 KB view raw
exp_m_smash.py β€” 2.5 KB view raw

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff applied in-guest, kernel rebuilt and installed: m len=264 now returns a 398-char reply (clamped to 199 bytes) with zero bytes changed outside remcomOutBuffer (gap reads back all zeros); m len=768 likewise clamped; M len=64-with-4-bytes writes exactly the 4 supplied bytes; M len=250-with-188-bytes writes exactly 188 bytes (no read past NUL or array end); negative lengths still benign. Protocol otherwise healthy in the same session.

run.fixed.log T1-T4 blocks with [RESULT] showing clamped lengths and no OOB deltas; fix.diff in this pack applies clean to the read-only sys/ tree
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #2: Mon Oct 19 01:25:11 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Exploit chain

console access -> stub session -> m, -> unbounded kernel BSS overwrite at remcomOutBuffer with hex-encoded copies of arbitrary kernel memory (129B for len 264, 1137B for len 768, unbounded upward); and/or M,: -> OOB read past the packet NUL and past remcomInBuffer[400] feeding hex2mem writes at an arbitrary kernel address

Evidence (decisive lines)

run.log T1/T1b/T2/T3/T4 blocks (exact content and boundary checks; [RESULT] pass:true); run.raw.log full serial byte stream (replayable with analyze2362b.py); run.fixed.log (m clamped to 398 chars with zero bytes changed outside remcomOutBuffer; M clamped to supplied data: 4B and 188B writes only)

PoC changes

Rewrote the seeds into poc2362.py with self-interference-aware verification: every command packet lands in remcomInBuffer and every m reply in remcomOutBuffer, so gap checks use single-shot reads, Rin evidence is read at offsets past the command text, and M scratch targets moved out of remcomOutBuffer into the unused dummy.9261 BSS (the seed placed them inside remcomOutBuffer where the verify read overwrites its own evidence at i>=128). T3 prediction rebuilt from a pre-read of the Rin tail plus the getpacket NUL position. The bad-checksum prefill trick (getpacket stores payload before validating) documents attacker control of the stale buffer that the M overread decodes.

Verified recommended fix

m: clamp length to (BUFMAX-1)/2 before mem2hex; M: clamp length to min(strlen(ptr)/2, (BUFMAX-1)/2) and reject negatives before hex2mem

Verdict

Reproduced on the live stock kernel. The m handler passes hexToInt-parsed length straight to mem2hex: 2length+1 bytes of hex text are written into the 400-byte static remcomOutBuffer with no bound - length 264 wrote 129 bytes past its end (gap bytes byte-equal to the reply tail, NUL boundary exact, remcomInBuffer tail holding overflow chars) and length 768 wrote 1137 bytes OOB across all of remcomInBuffer plus buf.7900/dsi_debug and 625 bytes into dummy.9261, all byte-verified against the reply and repaired. The M handler consumes 2length chars from remcomInBuffer regardless of what was supplied: declared 64 with 4 data bytes wrote 60 bytes decoded from past the packet NUL (exact predicted fa/aa pattern from an attacker-prefilled stale buffer), and declared 250 with 188 data bytes read 62 bytes from past the 400-byte array end into adjacent BSS (exact predicted ef pattern) on the way to writing at an attacker-chosen kernel address. Negative lengths are accepted by both parsers (benign effect). Reach requires the kernel debug console (physical serial flags 0x80/0x90, dcons, or DDB/root entry) - operator/physical, no unprivileged or network path - so Medium/dos with a genuine unbounded kernel-BSS overwrite plus OOB-read-fed arbitrary-address write inside that boundary.