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)®isters + 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)®isters + 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 viaM. - Privileges gained or impact: The marginal impact is corruption of the
gdb_handle_exceptionstack 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_regspointer that could redirect control when the handler writes registers back (lines 633-656). No privilege escalation beyond the designedMprimitive. Negative-regno is caught by fault protection and is a robustness / hardening issue. - Required config or capabilities:
options KGDBandRB_GDBboot flag or rootsysctl debug.enter_debugger=gdbplus 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 ®isters + 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.
Recommended fix
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, ®no)
- && *ptr++ == '='
- && regno < NUM_REGS)
+ if (hexToInt (&ptr, ®no)
+ && *ptr++ == '='
+ && regno >= 0
+ && regno < NUMREGBYTES / 8)
{
/* JG */
hex2mem (ptr, (vm_offset_t)®isters + 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
sys/cpu/x86_64/misc/x86_64-gdbstub.c:438βNUMREGBYTES = sizeof(registers) = 152sys/cpu/x86_64/misc/x86_64-gdbstub.c:442βNUM_REGS = 22(wrong bound)sys/cpu/x86_64/misc/x86_64-gdbstub.c:454-475βstruct x86_64regs(152 bytes)sys/cpu/x86_64/misc/x86_64-gdbstub.c:565-573βPhandler- CWE-129 Improper Validation of Array Index
Timeline
- 2026-07-14 Discovered during automated audit.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1080 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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/.
Recommended fix
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
fixedVALIDATED 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
Confirmed kernel references
- s
- y
- s
- /
- c
- p
- u
- /
- x
- 8
- 6
- _
- 6
- 4
- /
- m
- i
- s
- c
- /
- x
- 8
- 6
- _
- 6
- 4
- -
- g
- d
- b
- s
- t
- u
- b
- .
- c
- :
- 5
- 6
- 9
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
No comments yet.