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)
PoC verification
Evidence pack
findings/poc/DF-2362 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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
fixedfix.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
Confirmed kernel references
- sys/cpu/x86_64/misc/x86_64-gdbstub.c:118
- sys/cpu/x86_64/misc/x86_64-gdbstub.c:280-281
- sys/cpu/x86_64/misc/x86_64-gdbstub.c:313-328
- sys/cpu/x86_64/misc/x86_64-gdbstub.c:333-347
- sys/cpu/x86_64/misc/x86_64-gdbstub.c:579-594
- sys/cpu/x86_64/misc/x86_64-gdbstub.c:588
- sys/cpu/x86_64/misc/x86_64-gdbstub.c:596-614
- sys/cpu/x86_64/misc/x86_64-gdbstub.c:604-607
Detail
Exploit chain
console access -> stub session -> m
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.
No comments yet.