Skip to content

Bounds check RLE runs in DecodeRLE - #1171

Merged
xusheng6 merged 2 commits into
devfrom
test_1170_decoderle_bounds
Aug 13, 2026
Merged

Bounds check RLE runs in DecodeRLE#1171
xusheng6 merged 2 commits into
devfrom
test_1170_decoderle_bounds

Conversation

@xusheng6

Copy link
Copy Markdown
Member

Fixes #1170

DecodeRLE did not bounds check its '*' branch. An RLE run is <char>*<count>, so it
needs a count byte after the '*' and a character before it to repeat; neither was
checked, and both come from the remote stub.

  • '*' as the final byte read m_data[index + 1] one past the buffer.
  • '*' before any literal character evaluated result[result.size() - 1] on an empty
    string, indexing SIZE_MAX.

RLE is a standard RSP feature that gdbserver uses to compress repeated bytes, so this is
reachable from ordinary traffic, not just a hostile stub.

Change

Reject both cases and log, rather than decoding past the end. A run marker with nothing to
repeat means the packet is malformed. Once #1166 lands this should raise
DebugProtocolError like the other malformed-field paths — kept to a log here so the
memory-safety fix can land independently of that PR.

Testing

Under ASan, before and after:

input before after
"*A" (leading *) abort ok
"*" abort ok
"AB*" (trailing *) OOB read, not ASan-visible ok
"T05;*A" ok ok

Valid RLE still expands: "A*!" -> "AAAAA", "0* " -> "0000", and packets with no
'*' are unchanged.

Found by fuzzing the GDB adapter against a live Corellium stub through a mutating proxy;
the path hit was GetModuleList -> GetRemoteFile -> HostFileIO -> ReceiveRspData -> DecodeRLE.

An RLE run is "<char>*<count>", so decoding one needs both a count byte after the
'*' and a preceding character to repeat. Neither was checked, and both are stub
controlled:

- '*' as the final byte read m_data[index + 1] one past the buffer.
- '*' before any literal character evaluated result[result.size() - 1] on an empty
  string, indexing SIZE_MAX. ASan reports a stack-buffer-overflow for this one.

Reject both instead, and log, since a run marker with nothing to repeat means the
packet is malformed rather than merely unusual. Once #1166 lands this should raise
DebugProtocolError like the other malformed-field paths; keeping it to a log here
so the memory-safety fix can land independently.

Fixes #1170
@xusheng6
xusheng6 requested a review from bdash August 13, 2026 17:00
@xusheng6

Copy link
Copy Markdown
Member Author

@bdash can you have a look at this PR and #1173? These are found alongside another one you approved earlier when I was fuzzing our RSP implementation

Comment thread core/adapters/rspconnector.cpp Outdated
Comment thread core/adapters/rspconnector.cpp Outdated
Comment thread core/adapters/rspconnector.cpp Outdated
@xusheng6
xusheng6 requested a review from bdash August 13, 2026 21:05
BinaryDecode had the same unchecked count byte and empty-result indexing as
DecodeRLE, plus an unchecked read for the byte after a '}' escape. Share one
helper between both decoders, reject counts outside the printable range, and
return an empty RspData instead of partially decoded data.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@xusheng6
xusheng6 force-pushed the test_1170_decoderle_bounds branch from 4c65ec3 to 9cbcce3 Compare August 13, 2026 21:07
@xusheng6
xusheng6 merged commit 6229b34 into dev Aug 13, 2026
1 check passed
@xusheng6
xusheng6 deleted the test_1170_decoderle_bounds branch August 13, 2026 21:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Out-of-bounds read in RspConnector::DecodeRLE on malformed RLE runs

2 participants