Skip to content

Fix remaining buffer overflows in the CLI command paths - #3848

Open
Valentin-Metz wants to merge 1 commit into
meshcore-dev:devfrom
Valentin-Metz:fix/cli-parse-buffer-overflows
Open

Valentin-Metz wants to merge 1 commit into
meshcore-dev:devfrom
Valentin-Metz:fix/cli-parse-buffer-overflows

Conversation

@Valentin-Metz

Copy link
Copy Markdown

Follow-up to #3807. Auditing the full CLI dispatch chain (onPeerDataRecv → handleCommand → CommonCLI/CommonRadioPrefs → reply buffer) for the same class of overflow turned up more sites, reachable with the same long-input technique. Remote datagrams decrypt to commands of up to 171 chars; the serial/ethernet input loops accept up to 159.

Overflow sites fixed

Site Trigger Overflow
CommonRadioPrefs.cpp set radio set radio + ≥123 junk chars ~30 B stack, before any param validation — first handler in the chain, no prefix needed
CommonCLI.cpp tempradio tempradio + junk ~29 B, 132-byte member tmp[] → corrupts adjacent members of the node object
CommonCLI.cpp sensor set sensor set + junk same
CommonCLI.cpp extra.sf (USE_LR2021 builds) set extra.sf + junk same
CommonCLI.cpp get <unknown> fallback get + 167 junk chars 8 B past the temp[169] sized in #3807 — the previous fix is defeated by this path
repeater/room-server/sensor main.cpp reply[160] prefixed aa|regions 3 B — these buffers feed the same prefix-reflecting handleCommand() fixed in #3807, but weren't resized
simple_secure_chat public <msg> long serial line ~350 B — sprintf first, truncation line runs only after the overflow
DynamicConfigSerializer::getByKey caller passing sizeof(buf) 1 B — value[max_len] = 0 writes one past a max_len-sized buffer (latent: current callers pass sizeof(buf)-1)

Changes

  • strcpy → StrHelper::strncpy(tmp, …, sizeof(tmp)) at the four tmp[] copy sites (existing repo idiom; truncation is safe — the copied text is only parsed for numeric params/keys).
  • sprintf(reply, "??: %s", config) → snprintf(reply, 160, …).
  • char reply[160] → char reply[164] in the example serial/ethernet loops (3-byte reflected prefix + 160-byte reply + NUL), same comment style as Fix stack-buffer overflow in CLI reply buffers #3807.
  • simple_secure_chat: sprintf-then-truncate → bounded snprintf.
  • getByKey: strncpy(value, …, max_len - 1); value[max_len - 1] = 0;.

Reachability

  • The tmp[] and get ??? sites are reachable over the remote admin CLI (LoRa datagram), and also from serial and the ethernet TCP CLI (ETHERNET_ENABLED builds).
  • The main.cpp reply[164] buffers cover serial + ethernet.
  • secure_chat is serial-only. getByKey is latent.

Verification

Host ASan harness compiling the real CommonCLI.cpp, CommonRadioPrefs.cpp, RegionMap.cpp etc. with the exact reply-buffer layouts of onPeerDataRecv() and the serial main() loops:

  • Before this change (pristine sources): ASan stack-buffer-overflow on all four patchable paths — CommonRadioPrefs.cpp:57 (set radio), CommonCLI.cpp:255 (tempradio), CommonCLI.cpp:311 (sensor set), CommonCLI.cpp:822 (get ???, 8 bytes past temp[169]); the secure_chat sprintf-then-truncate shape likewise.
  • After: all cases pass clean; replies are correct truncated outputs (Error, invalid radio params, etc.), prefixed regions listing unaffected (reply_len=162).
  • pio test -e native: 50/50 pass, including test_config_serializer which exercises getByKey.

Follow-up to meshcore-dev#3807: auditing the full CLI dispatch chain for the same
class of overflow turned up more sites, all reachable with the same
long-input technique (remote datagrams decrypt to commands of up to
171 chars; the serial/ethernet buffers accept up to 159):

- CommonRadioPrefs::handleCommand 'set radio' copied the command tail
  into a 132-byte stack tmp[] with strcpy() before any parameter
  validation -- ~30 bytes past the end (remote admin CLI, no prefix
  needed).
- CommonCLI::handleCommand 'tempradio' / 'sensor set' and
  handleSetCmd 'extra.sf' copied into the 132-byte member tmp[] with
  strcpy(), corrupting adjacent members of the node object.
- CommonCLI::handleGetCmd's unknown-config fallback did
  sprintf(reply, "??: %s", config) -- a remote 'get <167 chars>'
  writes 8 bytes past the temp[169] sized in meshcore-dev#3807.
- The serial/ethernet reply buffers in the repeater/room-server/sensor
  examples were char[160] but feed the same prefix-reflecting
  handleCommand() fixed in meshcore-dev#3807: a prefixed 'aa|regions' writes
  3 + 160 = 163 bytes plus NUL. Now 164 with the same sizing comment.
- simple_secure_chat 'public <msg>' sprintf'ed node_name + user text
  into a 160-byte region and only truncated after the fact -- a long
  serial line overflows by ~350 bytes before the truncation runs.
  Replaced with a bounded snprintf.
- DynamicConfigSerializer::getByKey() terminated with
  value[max_len] = 0 -- one past the end of a max_len-sized buffer
  for any caller passing sizeof(buf) (current callers pass
  sizeof(buf)-1, so latent).

Bounds the copies with StrHelper::strncpy()/snprintf() following the
existing repo idiom, and sizes the example reply buffers for the
full worst case.

Verified with a host ASan harness compiling the real CommonCLI,
CommonRadioPrefs and RegionMap sources with the exact reply-buffer
layouts of onPeerDataRecv() and the serial main loops: 'set radio'
+ 171 junk chars, 'tempradio'/'sensor set' + max-length input and
'get' + 167 junk chars each overflowed before this change and pass
clean after it; the secure_chat sprintf-then-truncate shape likewise.
pio test -e native: 50/50 pass (incl. config serializer getByKey).
@Valentin-Metz
Valentin-Metz force-pushed the fix/cli-parse-buffer-overflows branch from 443fb61 to 289f4dc Compare November 15, 2026 06:07

This branch has not been deployed

No deployments
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.

1 participant