Skip to content

Fix companion crash on a short CMD_SEND_CHANNEL_TXT_MSG frame - #3643

Open
marcelverdult wants to merge 1 commit into
meshcore-dev:devfrom
marcelverdult:upstream-candidate/companion-channel-txt-min-len
Open

marcelverdult wants to merge 1 commit into
meshcore-dev:devfrom
marcelverdult:upstream-candidate/companion-channel-txt-min-len

Conversation

@marcelverdult

Copy link
Copy Markdown
Contributor

CMD_SEND_CHANNEL_TXT_MSG reads txt_type, channel_idx and a 4 byte timestamp, so the frame needs 7 bytes before the text, but the branch has no length guard. For a shorter frame len - i is computed in size_t, wraps, and reaches sendGroupMessage() as a negative int. The clamp there (text_len + prefix_len > MAX_TEXT_LEN) lets a negative value through, and the following memcpy() turns it back into a size_t: on a 32 bit target a 3 byte frame asks for 4294967292 bytes into a 197 byte stack buffer.

Two guards, because there are two mistakes:

  • the branch condition gains len >= 7, the way the neighbouring CMD_SEND_TXT_MSG states len >= 14. A shorter frame falls through to the existing final else;
  • sendGroupMessage() refuses a negative length, the way sendGroupData() in the same file already does. Today the companion frame handler is its only caller, so this protects future callers and is what makes the fix testable on the host.

A frame of exactly 7 bytes still sends an empty message, as before.

Error code. A too short frame is answered with ERR_CODE_UNSUPPORTED_CMD, which is what the other branches that guard their length with && len >= N answer. ERR_CODE_ILLEGAL_ARG would describe it better, but switching only this branch would make it the odd one out, and switching all of them is a visible protocol change that does not belong in a crash fix.

Evidence

On a LilyGo T-Echo, USB companion build, each image marked through FIRMWARE_BUILD_DATE and confirmed with ver before the run.

Before, on dev 428a95c:

CH_TXT len=7    -> RESP_CODE_OK
CH_TXT len=3    -> NO REPLY
DEVICE_QUERY    -> NO REPLY  (three times over 8 s)

The node did not come back; neither a 1200 baud touch nor the reset button recovered it, only unplugging the cable.

After, on this branch:

CH_TXT len=1 .. len=6   -> ERR_CODE_UNSUPPORTED_CMD (each one)
uptime                  -> 23 s before, 35 s after, so no restart
CH_TXT len=7 (empty)    -> RESP_CODE_OK
CH_TXT len=12 (text)    -> RESP_CODE_OK, node transmits
CH_TXT txt_type=1       -> ERR_CODE_UNSUPPORTED_CMD, unchanged
DEVICE_QUERY            -> answers

On the host, a new native_chat environment builds the real Utils, Packet, Dispatcher, Mesh, BaseChatMesh, AdvertDataHelpers and TxtDataHelpers against fakes for radio, clock, RNG and packet manager:

before: Program received signal SIGBUS (Bus error: 10)
after:  native, native_chat, native_kiss_modem: 62 test cases: 62 succeeded

The host test covers sendGroupMessage(); the frame handler guard itself is covered only by the hardware run above, because MyMesh.cpp cannot be built on the host without stand-ins for board, radio, sensors and store. Companion builds checked on all five toolchains that compile this file (T-Echo nRF52, Heltec V3 ESP32, RAK11310 RP2040, Wio-E5 STM32, Xiao C6).

The first commit adds the tests and is red on its own; the second is the fix; the third adds native_chat to the unit test workflow so the suite runs in CI.

Related open PRs: none touches the CMD_SEND_CHANNEL_TXT_MSG branch or the body of sendGroupMessage(). New callers in #3529 (strlen(text)) and #2496 (an input length only decremented while > 0) cannot pass a negative length. #3638 adds two steps to .github/workflows/run-unit-tests.yml right below the line this PR extends, a trivial textual conflict whichever lands second.

@ripplebiz

Copy link
Copy Markdown
Member

Thanks for the extra param guards. (which are correct)

The unit tests, I have to say, are of negative value, though. It's a very verbose setup just to put some cases around one simple condition (the BaseChatMesh::sendGroupMessage(), text_len < 0).

Remember that all of that mocking, if committed, would have to be maintained from this point on, which adds significant load on whoever is making future changes, and the tests themselves add very little value.

CMD_SEND_CHANNEL_TXT_MSG reads txt_type, channel_idx and a 4 byte timestamp, so
the frame needs 7 bytes before the text starts, and the branch had no length
guard. For a shorter frame "len - i" is computed in size_t, wraps, and reaches
sendGroupMessage() as a negative int. The clamp there tests
"text_len + prefix_len > MAX_TEXT_LEN", which a negative value passes, and the
following memcpy() converts the length back to a size_t: a 3 byte frame asks for
4294967292 bytes into a 197 byte stack buffer.

Two guards, because there are two mistakes. The branch now states its minimum
like most other branches of handleCmdFrame do, so a frame below 7 bytes is
answered through the existing path instead of being parsed. And
sendGroupMessage() refuses a negative length the way sendGroupData() in the same
file already does, which protects every other caller.

A frame of exactly 7 bytes keeps working and sends an empty message, as before.

Evidence. On a T-Echo running dev 428a95c, built with
FIRMWARE_BUILD_DATE "f1-dev" and confirmed by "ver => v1.18.0 (Build: f1-dev)":

  CH_TXT len=7    -> RESP_CODE_OK
  GET_STATS core  -> uptime=78s
  CH_TXT len=3    -> NO REPLY
  DEVICE_QUERY    -> NO REPLY  (three times over 8 s, node never came back)
@marcelverdult
marcelverdult force-pushed the upstream-candidate/companion-channel-txt-min-len branch from 456970d to 56b5f4e Compare October 22, 2026 09:33
@marcelverdult

Copy link
Copy Markdown
Contributor Author

removed the tests. thx for the heads up

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.

2 participants