Skip to content

StoreForwardModule::historyAdd: memcpy source size, not buffer capacity#10250

Merged
thebentern merged 1 commit into
meshtastic:developfrom
nightjoker7:fix/storeforward-historyadd-oob
Apr 23, 2026
Merged

StoreForwardModule::historyAdd: memcpy source size, not buffer capacity#10250
thebentern merged 1 commit into
meshtastic:developfrom
nightjoker7:fix/storeforward-historyadd-oob

Conversation

@nightjoker7

Copy link
Copy Markdown
Contributor

Problem

In src/modules/StoreForwardModule.cpp::historyAdd():

this->packetHistory[...].payload_size = p.payload.size;
...
memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN);

The module correctly records payload_size = p.payload.size, but the very next memcpy copies the full buffer capacity (DATA_PAYLOAD_LEN = 237 bytes), not the actual payload length. For any packet with payload.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_LENp.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:

  • Leaks adjacent memory contents into replayed packets (info disclosure potential)
  • Silently changes payload content between what sender sent and what S&F replays
  • A latent correctness bug independent of the read

Risk

Minimal. One-token change. p.payload.size is already used two lines above to set payload_size on 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).

`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.
@github-actions github-actions Bot added the bugfix Pull request that fixes bugs label Apr 23, 2026
@thebentern thebentern merged commit a6b1a69 into meshtastic:develop Apr 23, 2026
3 checks passed
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Pull request that fixes bugs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants