Compile-time-enforced bounds for fixed-layout MSP SET handlers - #11823
Conversation
MSP_SET_RC_TUNING, MSP2_INAV_SET_RATE_PROFILE, and MSP2_COMMON_SET_MSP_RC_INFO each relied on a hand-written dataSize guard with no compiler-enforced link to what the handler actually reads, so a future field addition could silently drift the two out of sync. Replace the imperative field-by-field reads with a packed wire struct per message, a STATIC_ASSERT pinning its size, and a single sbufReadDataSafe call, moving constrain()/clamp logic to the assignment step. Also fixes a latent bug this surfaced: sbufReadData doesn't advance the buffer pointer, so RC_TUNING's second read for its optional 11th byte was silently re-reading byte 0 instead of byte 10.
|
ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing |
PR Summary by QodoEnforce fixed MSP SET payload bounds at compile time
AI Description
Diagram
High-Level Assessment
Files changed (1)
|
Code Review by Qodo🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewTip of the day💡 Did you know, you can hide the parts of a finding you never read, like the evidence or the agent prompt |
|
Test firmware build ready — commit Download firmware for PR #11823 247 targets built. Find your board's
|
|
RAM / Flash usage vs. base branch — commit
See RAM/flash optimization guide for techniques to reduce usage. |
|
@xznhj8129 you are very familiar with the MSP handling code. Does this look good to you, for robustness in case of data corruption or sender and receiver have different version or similar? |
Oh yeah this looks much better, having the defined struct like this instead of reading it piecemeal makes it a lot easier to handle. Size from sizeof, safe reads, constrain, the packed struct; basically all the MSP handlers should be like this. Those typedef structs are the kind of thing who would belong in an msp definition header as the main message library. |
|
Thanks! Note to self - ensure these all allow reading a message with a different length than expected, from a different version of the MSP message. |
…handlers MSP payloads only ever gain fields at the end, so a configurator from a newer firmware version sending a longer message must be accepted by older firmware: apply the known fields and ignore the trailing bytes. Change MSP_SET_RC_TUNING and MSP2_INAV_SET_RATE_PROFILE from strict == gates to lenient >= gates; rcYawExpo8 stays conditional on dataSize > 10.
|
Phase 4: lenient gates per forward-compat policy — implemented + SITL-verified. Following the 2026-08-27 policy decision (MSP payloads only ever gain fields at the END; a newer configurator's longer message must be accepted by older firmware — apply known fields, ignore trailing bytes), the gates are now deliberately lenient:
Implementation: commit SITL round-trip verification (extended
Note: |
Summary
MSP_SET_RC_TUNING,MSP2_INAV_SET_RATE_PROFILE, andMSP2_COMMON_SET_MSP_RC_INFOeach relied on a hand-writtendataSizeguard with no compiler-enforced link to what the handler body actually reads — a future field addition could silently drift the guard out of sync with the read sequence. This converts all three to a packed wire struct +STATIC_ASSERT(sizeof(...) == N, ...)+ a singlesbufReadDataSafecall, movingconstrain()/clamp logic to the assignment step. No intended behavior change for well-formed input.Changes
mspSetRcTuning_t,mspSetRateProfile_t,mspSetMspRcInfo_tpacked structs withSTATIC_ASSERT-pinned sizessbufReadDataSafe+ struct-field assignmentMSP_SET_RC_TUNING's legacy two-length wire format (10 or 11 bytes) is handled with the fixed 10-byte struct plus a conditional second read for the trailingrcYawExpo8byteMSP2_COMMON_SET_MSP_RC_INFOkeeps its originaldataSize >= 15(not narrowed to==) to preserve existing lenient-trailing-bytes behaviormspReadRates(), dead code after both its callers were converted to inlineconstrain()callssbufReadData/sbufReadDataSafedon't advance the buffer's read pointer (unlikesbufReadU8/U16/U32), soMSP_SET_RC_TUNING's second read for its optional 11th byte was silently re-reading byte 0 instead of byte 10, corruptingrcYawExpo8on any 11-byte legacy frame. Fixed with an explicitsbufAdvance()between the two reads — this idiom is already used elsewhere in this file and intelemetry/msp_shared.c.Testing
MIN()vsconstrain()vs no-clamp applies todynPIDin each handler — deliberately different betweenMSP_SET_RC_TUNINGandMSP2_INAV_SET_RATE_PROFILE)MSP_SET_RC_TUNING(both 10- and 11-byte variants) andMSP2_INAV_SET_RATE_PROFILE, read back via the corresponding GET commands, confirmed exact match including clamp behavior — this test is what caught thesbufAdvancebug above before it shippedinav-code-reviewpass: approved, no correctness issues; re-derived all three struct layouts from scratch against the pre-diff code and confirmed a full-tree grep found no other instance of the double-sbufReadDataSafe-without-advance patternRelated Issues
Related to #11672
Sibling of #11822 (fix-msp-sensor-command-bounds-check, already merged/open)