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

P command register-index check (regno < NUM_REGS=22) allows stack OOB write past the registers struct

Field Value
ID DF-1080
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-129 Improper Validation of Array Index
File sys/cpu/x86_64/misc/x86_64-gdbstub.c
Lines 438, 442, 454-475, 561-573
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 P (set one register) command bounds the register number with regno < NUM_REGS where NUM_REGS is 22, but it indexes the 152-byte local struct x86_64regs registers as (vm_offset_t)&registers + regno * 8. Only regno 0..18 (offsets 0..144) fit inside the struct; regno 19, 20, 21 write 8 bytes each at offsets 152 / 160 / 168, i.e. past the struct into adjacent stack. There is also no lower-bound check, so a negative regno produced by hexToInt integer wrap indexes a wrapped (huge) address.

Root cause

struct x86_64regs (lines 454-475) is 152 bytes: 16 GP regs (128) + rip (8) + rflags (8) + cs (4) + ss (4). NUMREGBYTES = sizeof(registers) = 152 (line 438). NUM_REGS is hard-coded to 22 (line 442) but is only used as the P-command bound (line 569).

The P handler (lines 565-573) does hex2mem(ptr, (vm_offset_t)&registers + regno * 8, 8) assuming 22 eight-byte slots = 176 bytes, but the struct is only 152 bytes. regno=18 maps to offset 144 (cs+ss, the last 8 bytes, valid); regno=19 β†’ offset 152 (one past the struct), regno=20 β†’ 160, regno=21 β†’ 168: each writes 8 bytes past registers into the stack frame of gdb_handle_exception (adjacent locals such as ptr / addr / length / sigval at lines 450-453, and potentially saved frame / return address depending on layout).

The bound also lacks regno >= 0; hexToInt (lines 383-406) can return a negative int via unbounded left-shift wrap, and regno * 8 for negative regno sign-extends to a huge vm_offset_t offset (the setjmp / db_write_bytes fault protection in set_char catches the bogus address and returns -1, but the code ignores hex2mem's return value and still replies OK).

Threat model & preconditions

  • Attacker position: Same gating as DF-1079: reachable only inside an active KGDB session (root-entered or panic + RB_GDB) by the serial operator, who already has designed arbitrary kernel write via M.
  • Privileges gained or impact: The marginal impact is corruption of the gdb_handle_exception stack frame β€” at minimum a hang / crash on resume (DoS on an already-faulted kernel), and depending on compiler frame layout a corrupted saved return address / raw_regs pointer that could redirect control when the handler writes registers back (lines 633-656). No privilege escalation beyond the designed M primitive. Negative-regno is caught by fault protection and is a robustness / hardening issue.
  • 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

Reproduce in an active KGDB session (boot with -g or sysctl debug.enter_debugger=gdb as root, then trigger any trap / panic). Connect to the serial port, ACK the T05 stop packet with +, then send a P packet with an out-of-range register number, e.g. $P13=4141414141414141#XX sets regno 0x13 = 19, which writes 8 bytes (AAAAAAAA) at &registers + 152 β€” one byte past the struct. $P14=... and $P15=... hit offsets 160 / 168. Send $c#63 to continue; on resume the corrupted stack frame causes a fault / hang (capture in run.log / dmesg).

A negative index $Pffffffff=... wraps regno to -1; set_char faults on the wrapped address, hex2mem returns NULL (ignored), and the stub replies OK with no write.

Build & run

PoC harness drops into findings/poc/DF-1080/ as a small serial sender.

sudo sysctl debug.enter_debuger=gdb
# In a separate terminal:
python3 kgdb_p_oob_poc.py /dev/ttyU0

Expected output

Guest hangs or panics on resume after the P15 / c sequence. Stack-frame corruption in gdb_handle_exception is observable via dmesg or kgdb post-mortem.

Impact

Stack OOB write within gdb_handle_exception during an active KGDB session. Robustness / DoS on an already-faulted kernel. Low severity.

Bound regno to the actual struct capacity (NUMREGBYTES / 8 = 19, i.e. regno 0..18) and reject negatives.

--- a/sys/cpu/x86_64/misc/x86_64-gdbstub.c
+++ b/sys/cpu/x86_64/misc/x86_64-gdbstub.c
@@ -565,9 +565,9 @@
        ptr = &remcomInBuffer[1];

-       if (hexToInt (&ptr, &regno)
-       && *ptr++ == '='
-       && regno < NUM_REGS)
+       if (hexToInt (&ptr, &regno)
+       && *ptr++ == '='
+       && regno >= 0
+       && regno < NUMREGBYTES / 8)
          {
        /* JG */
        hex2mem (ptr, (vm_offset_t)&registers + regno * 8, 8);

NUMREGBYTES / 8 evaluates to 152 / 8 = 19, covering regno 0..18 (the last 8-byte write lands on the cs+ss pair at offset 144..151, fully inside the struct). NUM_REGS (22) is now unused on this path and may be removed or corrected for documentation; PC / SP / FP (lines 439-441) are independent and unaffected.

References

Timeline

  • 2026-07-14 Discovered during automated audit.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

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

DF-1080 source-confirmation

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

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

Mechanism

gdbstub P cmd stack OOB write: regno<NUM_REGS=22 but struct is 152 bytes; regno 19-21 writes past struct into stack frame. 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:569. combined-70 fix kernel: NK_DONE rc=0 (0 errors, -Werror).

PoC changes

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

Verified recommended fix

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

Verdict

REAL: gdbstub P cmd bounds regno regno 19-21 stack OOB write. KGDB-session-only. confirmed.