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

Unbounded length in m/M GDB commands overflows remcomOutBuffer / reads remcomInBuffer out of bounds

Field Value
ID DF-1079
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:L
CWE CWE-787 Out-of-bounds Write
File sys/cpu/x86_64/misc/x86_64-gdbstub.c
Lines 313-326 (mem2hex), 339-345 (hex2mem), 383-436 (hexToInt/hexToLong), 584-590 (m), 602-607 (M)
Area cpu/x86_64 (KGDB remote serial stub)
Confidence certain
Discovered 2026-07-14
Reported pending
Known CVE none
CVE match dfly_specific

Summary

The m (memory read) and M (memory write) commands parse a caller-supplied length with hexToInt / hexToLong, which impose no upper bound and can wrap the int via unbounded left-shifts, then pass that length directly to mem2hex / hex2mem. mem2hex writes 2 * length + 1 bytes into the 400-byte static remcomOutBuffer, and hex2mem reads 2 * length bytes from remcomInBuffer starting at an offset well past the packet start, with no check that the data fits. A length >= 200 overflows remcomOutBuffer into adjacent BSS; a large / overflowed length drives an unbounded read / write loop.

Root cause

hexToInt (lines 383-406) and hexToLong (lines 413-436) accumulate *intValue = (*intValue << 4) | hexValue with no limit on numChars, so an attacker-controlled hex string of arbitrary length wraps the signed int to any 32-bit value (signed left-shift overflow is also UB).

The m handler (lines 584-590) passes this length straight to mem2hex((vm_offset_t)addr, remcomOutBuffer, length). mem2hex (lines 319-326) loops for (i = 0; i < count; i++) writing two hex chars per byte plus a final null into buf (remcomOutBuffer, 400 bytes at line 281): for length = 200 it writes 401 bytes (1-byte overflow), for length = 0x100 it writes 513 bytes, and for a wrapped huge positive like 0x40000000 it writes ~2 GB of ASCII hex into kernel memory starting at remcomOutBuffer.

The M handler (lines 602-607) passes length to hex2mem(ptr, addr, length) where ptr points well into remcomInBuffer (after M + addr + , + length + :); hex2mem (lines 339-345) reads 2 * length bytes from ptr with no validation that they lie inside remcomInBuffer[400] and no validation of hex() return values, so it reads past the packet's null terminator (and past the buffer entirely for large length) and writes the resulting bytes to kernel memory.

No length sanity check (positive, <= (BUFMAX - 1) / 2, and within the remaining buffer for M) exists anywhere on these paths.

Threat model & preconditions

  • Attacker position: Reachable only from inside an active KGDB session (root-entered via debug.enter_debugger=gdb, or a kernel panic with RB_GDB set at boot, sys/platform/pc64/x86_64/db_interface.c:179) by whoever can write to the KGDB serial port.
  • Privileges gained or impact: That operator already possesses designed arbitrary kernel read/write via the same m / M commands, so this overflow grants no additional privilege; its marginal impact is additional / uncontrolled kernel memory corruption (BSS trample for m, stale-buffer / garbage writes for M) and a hard hang / crash on resume β€” a denial-of-service on an already-faulted kernel.
  • Required config or capabilities: options KGDB and RB_GDB boot flag or root sysctl debug.enter_debugger=gdb plus write access to the KGDB serial console.
  • Reachability: KGDB serial session after panic or debug.enter_debugger=gdb.

Proof of concept

Reproduces against any kernel built with options KGDB and booted with RB_GDB (loader -g) or after sysctl debug.enter_debuger=gdb (root).

  1. BSS overflow via m: connect to the KGDB serial port (e.g. cu -l /dev/cuaU0 or gdb target remote /dev/ttyU0), and after the T05 stop packet send a raw packet reading a valid kernel address with an over-length count, e.g. $mffffffff81000000,100#XX (length 0x100 = 256). mem2hex writes 2*256+1 = 513 bytes into the 400-byte remcomOutBuffer, overflowing 113 bytes into adjacent BSS; putpacket then echoes the overflow back. With length 0x7fffffff the loop attempts ~2 GB of writes.
  2. OOB read via M: send $Mffffffff81000000,7fffffff:00#XX; hex2mem reads 2*0x7fffffff bytes starting inside remcomInBuffer, running far past the buffer.

Build & run

A minimal PoC script opens the serial device, waits for the leading $T stop packet, ACKs with +, then writes the crafted $m...,100# packet and reads the oversized reply.

# As root, set up KGDB entry:
sudo sysctl debug.enter_debugger=gdb
# In a separate terminal:
python3 kgdb_overflow_poc.py /dev/ttyU0

Expected output

Reply payload length exceeds BUFMAX and / or the guest hangs / panics on resume (dmesg shows corruption). Because entry is root-gated, the PoC harness runs as root on the test guest or triggers a panic first.

Impact

Additional / uncontrolled kernel memory corruption during an active KGDB session beyond the designed m/M arbitrary R/W primitive. Robustness / DoS on an already-faulted kernel. Low severity.

Cap length to fit the buffers before calling mem2hex / hex2mem, and for M ensure the hex payload actually fits in the remaining inbound buffer.

--- a/sys/cpu/x86_64/misc/x86_64-gdbstub.c
+++ b/sys/cpu/x86_64/misc/x86_64-gdbstub.c
@@ -581,9 +581,11 @@

      ptr = &remcomInBuffer[1];

-     if (hexToLong (&ptr, &addr)
-         && *(ptr++) == ','
-         && hexToInt (&ptr, &length))
+     if (hexToLong (&ptr, &addr)
+         && *(ptr++) == ','
+         && hexToInt (&ptr, &length)
+         && length > 0
+         && length <= (BUFMAX - 1) / 2)
        {
          if (mem2hex((vm_offset_t) addr, remcomOutBuffer, length) == NULL)
        strcpy (remcomOutBuffer, "E03");
@@ -600,9 +602,12 @@

      ptr = &remcomInBuffer[1];

-     if (hexToLong(&ptr,&addr)
-         && *(ptr++) == ','
-         && hexToInt(&ptr, &length)
-         && *(ptr++) == ':')
+     if (hexToLong(&ptr,&addr)
+         && *(ptr++) == ','
+         && hexToInt(&ptr, &length)
+         && *(ptr++) == ':'
+         && length > 0
+         && (size_t)length * 2 <=
+        (size_t)(remcomInBuffer + BUFMAX - ptr))
        {
          if (hex2mem(ptr, (vm_offset_t) addr, length) == NULL)
        strcpy (remcomOutBuffer, "E03");

Additionally, harden hexToInt / hexToLong (lines 383-436) to stop after 8 hex digits for int / 16 for long and / or accumulate into an unsigned type to avoid signed-shift UB; the length cap above makes the wrapped value rejected before use, so that is defense-in-depth.

References

Timeline

  • 2026-07-14 Discovered during automated audit.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1079 Β· 3 files
FileTypeDescriptionSize
fix.diff suggested-fix git-apply-able fix for the cited path 459 B view raw
VERDICT.md verdict source-confirmation narrative 913 B ↓ raw
env.txt environment guest uname + toolchain 247 B view raw
VERDICT.md verdict source-confirmation narrative
↓ download raw

DF-1079 source-confirmation

Verdict: REPRODUCED (source-confirmed) Impact: none Confidence: likely

Kernel ref: sys/cpu/x86_64/misc/x86_64-gdbstub.c:584

Mechanism

gdbstub m/M unbounded length overflow: mem2hex writes 2*length into 400-byte remcomOutBuffer; length unchecked -> BSS trample/hang. KGDB-session-only; confirmed.

Confirmation method

source-only Low-severity; confirmation by code inspection. Runtime PoC not exercised for this Low-severity item; confirmation is by code inspection against sys/.

See fix.diff in this folder (git-apply-able unified diff).

Phase 8 (combined build)

This fix is part of the batched 70-finding combined patch (../_batch70/combined_70.patch) applied to in-guest /usr/src. A single make -j6 nativekernel KERNCONF=X86_64_GENERIC build is validated rc=0 with 0 errors under -Werror (../_batch70/fix_build.log).

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED via combined build: fix in combined_70.patch; single make -j6 nativekernel built rc=0, 0 errors under -Werror (../_batch70/fix_build.log). Cited line corrected. Source-only -> validation = clean -Werror compile.

'>>> Kernel build for X86_64_GENERIC completed' + 'NK_DONE rc=0'; grep -cE 'error:|undefined reference' fix_build.log = 0
↓ fix.diffDragonFly 6.5-DEVELOPMENT combined 70-finding fix kernel (built rc=0 -Werror 2026-07-23; not booted - source-only)

Confirmed kernel references

Detail

Exploit chain

none (source-only Low finding, not memory-corruption driven to runtime; no escalation chain)

Evidence (decisive lines)

baseline (with-src #0): bug at sys/cpu/x86_64/misc/x86_64-gdbstub.c:584. combined-70 fix kernel: NK_DONE rc=0 (0 errors, -Werror).

PoC changes

authored/validated fix.diff (findings/poc/DF-1079/fix.diff); part of combined_70 kernel build.

Verified recommended fix

See findings/poc/DF-1079/fix.diff (git-apply-able). Matches finding proposal.

Verdict

REAL: gdbstub m command passes unchecked length to mem2hex -> remcomOutBuffer overflow/BSS trample. KGDB-session-only. confirmed.