StoreForwardModule::historyAdd: memcpy source size, not buffer capacity#10250
Merged
thebentern merged 1 commit intoApr 23, 2026
Merged
Conversation
`memcpy(... p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN)`
reads past the actual payload when the incoming packet's payload is
shorter than `DATA_PAYLOAD_LEN` (237 bytes). The code just above
already records the correct size:
this->packetHistory[...].payload_size = p.payload.size;
but then the memcpy ignores that and copies the full buffer capacity,
pulling uninitialized / adjacent memory bytes into the history entry.
Those extra bytes are later rebroadcast whenever the Store & Forward
module replays the packet.
Fix: memcpy using `p.payload.size` (the actual payload length) instead
of the constant buffer capacity.
Classification: bounded out-of-bounds READ into the protobuf scratch
buffer. Not directly exploitable for RCE (the destination buffer is
also DATA_PAYLOAD_LEN), but leaks adjacent memory into replayed
packets and is a latent correctness bug.
thebentern
approved these changes
Apr 23, 2026
mariotti
pushed a commit
to mariotti/firmware
that referenced
this pull request
May 6, 2026
…ty (meshtastic#10250) `memcpy(... p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN)` reads past the actual payload when the incoming packet's payload is shorter than `DATA_PAYLOAD_LEN` (237 bytes). The code just above already records the correct size: this->packetHistory[...].payload_size = p.payload.size; but then the memcpy ignores that and copies the full buffer capacity, pulling uninitialized / adjacent memory bytes into the history entry. Those extra bytes are later rebroadcast whenever the Store & Forward module replays the packet. Fix: memcpy using `p.payload.size` (the actual payload length) instead of the constant buffer capacity. Classification: bounded out-of-bounds READ into the protobuf scratch buffer. Not directly exploitable for RCE (the destination buffer is also DATA_PAYLOAD_LEN), but leaks adjacent memory into replayed packets and is a latent correctness bug.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
In
src/modules/StoreForwardModule.cpp::historyAdd():The module correctly records
payload_size = p.payload.size, but the very nextmemcpycopies the full buffer capacity (DATA_PAYLOAD_LEN= 237 bytes), not the actual payload length. For any packet withpayload.size < DATA_PAYLOAD_LEN— which is the common case — this reads past the end of the valid bytes into adjacent memory in the protobuf decode scratch buffer.Those extra bytes are later rebroadcast verbatim when S&F replays the packet to a recipient.
Fix
memcpy(..., p.payload.bytes, p.payload.size);One character change (
meshtastic_Constants_DATA_PAYLOAD_LEN→p.payload.size).Classification
Bounded out-of-bounds read into decode scratch memory. Destination buffer is the same capacity so not directly exploitable for RCE, but:
Risk
Minimal. One-token change.
p.payload.sizeis already used two lines above to setpayload_sizeon the history entry. Matches the bounds that other decoders use.Testing
In local fleet build for weeks, no regressions. S&F continues to work; replayed packet payloads now match what the sender transmitted (previously had trailing garbage).