kernel/debug/gdbstub.c | 6 ++++++ 1 file changed, 6 insertions(+)
write_mem_msg() passes the length of an 'M' or 'X' packet straight to
kgdb_hex2mem() or kgdb_ebin2mem(). Both decode the payload in place at a
pointer into remcom_in_buffer[BUFMAX], and neither checks that the length
fits. A client on the KGDB I/O console can claim any length, regardless of
how much payload it actually sent, so the decode runs off the end of the
buffer and then copies the decoded bytes to the address the client chose.
On x86 BUFMAX is 1024, so a write that claims 512 bytes overruns
remcom_in_buffer during the in-place hex decode:
BUG: KASAN: global-out-of-bounds in kgdb_hex2mem+0x131/0x160
Read of size 1 at addr ffffffff87651bb5 by task sh/1
...
kgdb_hex2mem+0x131/0x160
write_mem_msg+0x308/0x3e0
gdb_serial_stub+0xd8b/0x3490
...
The buggy address belongs to the variable:
remcom_in_buffer+0x415/0x420
Bounds check the length against the space left in the input buffer:
if (length > (BUFMAX - (ptr - remcom_in_buffer)) / 2)
return -EINVAL;
gdb_cmd_memwrite() and gdb_cmd_binwrite() already turn a non-zero return
into an error reply, and a write over the limit never decoded valid data,
so an error reply loses nothing.
Fixes: dc7d55270521 ("kgdb: core")
Cc: stable@vger.kernel.org
Assisted-by: Hawkeye:GLM-5.3-flash
Assisted-by: Qoder:Qwen3.8-Max
Signed-off-by: Fang Xieyan <fangxy@xiaopeng.com>
---
This is the write-side sibling of the 'm' memread overflow in
gdb_cmd_memread() (remcom_out_buffer); both handlers trust the same
unvalidated length field. The read-side fix was posted separately
(<20260914180344.15485-1-fangxy@xiaopeng.com>).
Reproducer: attach to a kgdboc console and send an 'M' packet whose length
is above the (BUFMAX - header) / 2 limit but whose payload is short, e.g.
"M<addr>,200:4142434445464748" on x86. The stub decodes 2 * 512 bytes at
ptr regardless of the 8 bytes sent, so the in-place hex conversion runs off
remcom_in_buffer[1024]. Before this change KASAN reports a
global-out-of-bounds read in kgdb_hex2mem and the guest dies; after it the
stub answers E22 and the guest resumes.
The 'X' (binary) path through kgdb_ebin2mem() needs the same / 2 divisor,
not a looser one. It decodes in place at ptr and reads a second input byte
for every 0x7d escape marker it sees, so a claimed length of N bytes reads
up to 2 * N bytes at ptr, exactly like the hex path. Giving 'X' a divisor
of 1 would let an escaped payload run off remcom_in_buffer, which is the
overflow this patch removes.
Both cases were run on 704340f1cd0d (v7.3-rc4): x86_64 defconfig plus
CONFIG_KASAN_GENERIC, gcc 13.2.0, QEMU under TCG. The two kernels are built
from byte-identical .config files and differ only by this patch.
CONFIG_KGDB_KDB has to be off: dbg_kdb_mode starts at 1, so with kdb built
in a sysrq-g break lands in kdb_stub() and the packet is never parsed.
A minimal over-the-limit length reproduces better than a large one. The
trailing copy_to_kernel_nofault() copies through raw asm accessors that
KASAN does not instrument, so a huge length smashes .bss past the buffer
with no report and the kernel simply stops answering. length = 0x200 keeps
the in-place decode just past remcom_in_buffer[1024], which gives the
report above.
TCG note: an unrelated early-boot "wild-memory-access in _raw_spin_lock"
sometimes fires under TCG + KASAN during identify_cpu and reboots the guest
before it reaches the break-in. That is not this bug; re-running the boot
clears it and the packet then lands as described.
kernel/debug/gdbstub.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/kernel/debug/gdbstub.c b/kernel/debug/gdbstub.c
index e271a43..a67ca1a 100644
--- a/kernel/debug/gdbstub.c
+++ b/kernel/debug/gdbstub.c
@@ -373,6 +373,12 @@ static int write_mem_msg(int binary)
if (kgdb_hex2long(&ptr, &addr) > 0 && *(ptr++) == ',' &&
kgdb_hex2long(&ptr, &length) > 0 && *(ptr++) == ':') {
+ /*
+ * The in-place decode touches 2 * length bytes at ptr,
+ * inside remcom_in_buffer[BUFMAX].
+ */
+ if (length > (BUFMAX - (ptr - remcom_in_buffer)) / 2)
+ return -EINVAL;
if (binary)
err = kgdb_ebin2mem(ptr, (char *)addr, length);
else
--
2.50.1 (Apple Git-155)
Hi,
On Wed, Sep 16, 2026 at 1:03 AM Fang Xieyan <fangxy@xiaopeng.com> wrote:
>
> diff --git a/kernel/debug/gdbstub.c b/kernel/debug/gdbstub.c
> index e271a43..a67ca1a 100644
> --- a/kernel/debug/gdbstub.c
> +++ b/kernel/debug/gdbstub.c
> @@ -373,6 +373,12 @@ static int write_mem_msg(int binary)
>
> if (kgdb_hex2long(&ptr, &addr) > 0 && *(ptr++) == ',' &&
> kgdb_hex2long(&ptr, &length) > 0 && *(ptr++) == ':') {
> + /*
> + * The in-place decode touches 2 * length bytes at ptr,
> + * inside remcom_in_buffer[BUFMAX].
> + */
> + if (length > (BUFMAX - (ptr - remcom_in_buffer)) / 2)
> + return -EINVAL;
FWIW, my AI wasn't terribly happy with this patch. I've skimmed the AI
response, and my AI sure looks right to me. Given that your patch was
generated with AI assistance, I won't feel too bad pasting my AI's
response here. :-P If you think my AI is wrong or you think I should
look at all this more closely, let me know. Otherwise, I'll wait until
my AI is happy (or looks like it's full of crap) before delving too
deeply myself.
AI response follows:
While this correctly identifies a real out-of-bounds access in
write_mem_msg(), the fix conflates 'M' (hex memory write) with 'X'
(binary memory write). As written, this patch introduces a regression
for valid GDB binary writes, leaves kgdb_ebin2mem() vulnerable to
out-of-bounds reads on escaped payloads, and does not check against the
actual received packet length.
1. Regression: Breaks valid GDB 'X' packets larger than ~BUFMAX / 2
The comment and commit message state that the in-place decode touches
2 * length bytes at ptr and that a write over the limit never decoded
valid data. That is only true for 'M' packets (kgdb_hex2mem):
- In 'M' packets, each byte is encoded as 2 ASCII hex characters, and
kgdb_hex2mem() uses the upper half (ptr + length to
ptr + 2 * length - 1) as scratch space while decoding backwards. So
'M' genuinely requires 2 * length bytes in remcom_in_buffer[BUFMAX].
- In 'X' packets (kgdb_ebin2mem), the payload is sent as raw binary (1
byte per byte), except for '$', '#', '}', and '*' which are escaped
with 0x7d ('}'). kgdb_ebin2mem() decodes forward in-place into
ptr[0 ... length - 1] and does not use ptr + length ... ptr + 2 *
length as scratch space.
GDB enables 'X' binary downloads by default ("set remote
binary-download-packet on") specifically so it can pack nearly BUFMAX
bytes per packet instead of BUFMAX / 2. When writing a large object or
running "load", GDB routinely sends 'X' packets where length is between
BUFMAX / 2 and BUFMAX - header_len (e.g., length = 900 when BUFMAX =
1024).
With this patch, kgdb replies to those valid 'X' packets with $E22#...
(-EINVAL). GDB only falls back from 'X' to 'M' when the target returns
an empty packet (""), indicating 'X' is unsupported. An E22 reply
causes GDB to abort the memory write entirely ("Cannot access memory at
address ...").
2. Bug: kgdb_ebin2mem() can still overflow on 0x7d escape sequences
In kgdb_ebin2mem():
while (count-- > 0) {
c[size] = *buf++;
if (c[size] == 0x7d)
c[size] = *buf++ ^ 0x20;
size++;
}
Here count (length) is the number of decoded output bytes, not the
number of input bytes consumed from buf. Every 0x7d byte causes a
second *buf++ read without decrementing count.
If the check in write_mem_msg() is relaxed for 'X' to allow length up
to the remaining space in remcom_in_buffer, a packet with length <=
avail that contains K escape bytes (0x7d) will read length + K bytes
from buf. If length + K > avail, buf runs past the end of
remcom_in_buffer[BUFMAX], triggering the same KASAN global-out-of-bounds
read in kgdb_ebin2mem().
To fix this properly, kgdb_ebin2mem() needs an explicit end pointer
(buf_end) and must check buf < buf_end before both *buf++ dereferences
in the loop.
3. Incomplete bounds check against actual received packet length
As the commit message notes, a client can claim any length regardless
of how much payload it actually sent. However, checking against BUFMAX
only checks against the static buffer capacity, not the number of bytes
actually received by get_packet().
If a client sends a short packet (e.g., claiming length = 256 while
only sending 4 hex chars), length <= (BUFMAX - header) / 2 passes.
kgdb_hex2mem() then reads hundreds of bytes past the NUL terminator
placed by get_packet() into stale data left in remcom_in_buffer from
previous packets (where non-hex chars decode to -1 / 0xff via
hex_to_bin()) and writes 256 bytes of garbage into kernel memory.
Suggested approach:
Record the received packet length in get_packet() (note that binary 'X'
packets can contain raw 0x00 bytes, so strlen() cannot be used for 'X'
packets) and pass the packet end pointer (or available received bytes)
down:
- For hex writes (!binary): check that 2 * length fits within both the
received payload length and remcom_in_buffer[BUFMAX].
- For binary writes (binary): pass the packet end pointer into
kgdb_ebin2mem() and check buf < buf_end before each byte read in the
loop, returning -EINVAL if the input stream ends before count bytes
are decoded.
(Side note: while looking at other callers of kgdb_hex2mem() in
gdbstub.c, note that qRcmd handling in gdb_cmd_query() passes len (the
hex char count from strlen()) instead of len / 2 as count to
kgdb_hex2mem(), which causes kgdb_hex2mem() to touch 2 * len bytes and
overflow remcom_in_buffer[BUFMAX] whenever len > (BUFMAX - 6) / 2.)
© 2016 - 2026 Red Hat, Inc.