Skip to content

Compile-time-enforced bounds for fixed-layout MSP SET handlers - #11823

Open
sensei-hacker wants to merge 1 commit into
iNavFlight:maintenance-10.xfrom
sensei-hacker:harden-msp-fixed-handlers-compile-time-bounds
Open

Compile-time-enforced bounds for fixed-layout MSP SET handlers#11823
sensei-hacker wants to merge 1 commit into
iNavFlight:maintenance-10.xfrom
sensei-hacker:harden-msp-fixed-handlers-compile-time-bounds

Conversation

@sensei-hacker

Copy link
Copy Markdown
Member

Summary

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 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 single sbufReadDataSafe call, moving constrain()/clamp logic to the assignment step. No intended behavior change for well-formed input.

Changes

  • Add mspSetRcTuning_t, mspSetRateProfile_t, mspSetMspRcInfo_t packed structs with STATIC_ASSERT-pinned sizes
  • Replace each handler's imperative reads with guard + sbufReadDataSafe + struct-field assignment
  • MSP_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 trailing rcYawExpo8 byte
  • MSP2_COMMON_SET_MSP_RC_INFO keeps its original dataSize >= 15 (not narrowed to ==) to preserve existing lenient-trailing-bytes behavior
  • Remove mspReadRates(), dead code after both its callers were converted to inline constrain() calls
  • Fixes a latent bug this surfaced: sbufReadData/sbufReadDataSafe don't advance the buffer's read pointer (unlike sbufReadU8/U16/U32), so MSP_SET_RC_TUNING's second read for its optional 11th byte was silently re-reading byte 0 instead of byte 10, corrupting rcYawExpo8 on any 11-byte legacy frame. Fixed with an explicit sbufAdvance() between the two reads — this idiom is already used elsewhere in this file and in telemetry/msp_shared.c.

Testing

  • Built SITL successfully, no compiler warnings
  • Field-by-field review of all three handlers against the original imperative read sequence (byte offsets, and which of MIN() vs constrain() vs no-clamp applies to dynPID in each handler — deliberately different between MSP_SET_RC_TUNING and MSP2_INAV_SET_RATE_PROFILE)
  • SITL round-trip test (44/44 checks): sent distinct values for every field of both MSP_SET_RC_TUNING (both 10- and 11-byte variants) and MSP2_INAV_SET_RATE_PROFILE, read back via the corresponding GET commands, confirmed exact match including clamp behavior — this test is what caught the sbufAdvance bug above before it shipped
  • Independent inav-code-review pass: 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 pattern

Related Issues

Related to #11672

Sibling of #11822 (fix-msp-sensor-command-bounds-check, already merged/open)

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.
@sensei-hacker sensei-hacker added this to the 10.0 milestone Aug 25, 2026
@qodo-code-review

Copy link
Copy Markdown
Contributor

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Enforce fixed MSP SET payload bounds at compile time

🐞 Bug fix ✨ Enhancement 🕐 10-20 Minutes

Grey Divider

AI Description

• Enforces fixed MSP payload layouts with packed structs and compile-time size assertions.
• Preserves legacy lengths and clamping while safely mapping fields into runtime state.
• Advances legacy RC tuning reads correctly, preventing optional yaw expo corruption.
Diagram

graph TD
    A["MSP SET payload"] --> B{"Size valid?"} --> C["Packed wire struct"] --> D["Safe buffer copy"] --> E["Clamp fields"] --> F["Runtime state"]
Loading
High-Level Assessment

The packed wire-struct approach is appropriate because it directly couples accepted fixed payload sizes to compiler-checked layouts while preserving existing protocol behavior. Field-by-field safe reads would retain duplicated layout knowledge, and a generated decoder would add disproportionate complexity for three local handlers.

Files changed (1) +93 / -44

Bug fix (1) +93 / -44
fc_msp.cHarden fixed-layout MSP SET command decoding +93/-44

Harden fixed-layout MSP SET command decoding

• Adds packed, statically size-checked wire structs for RC tuning, rate profile, and MSP RC information payloads, then maps safely copied fields into runtime configuration with existing constraints preserved. Explicitly advances the RC tuning buffer before reading its optional legacy yaw-expo byte, fixing corruption caused by rereading byte zero, and removes the now-unused rate-reading helper.

src/main/fc/fc_msp.c

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

Tip of the day
💡 Did you know, you can hide the parts of a finding you never read, like the evidence or the agent prompt

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@github-actions

Copy link
Copy Markdown

Test firmware build ready — commit 572035e

Download firmware for PR #11823

246 targets built. Find your board's .hex file by name on that page (e.g. MATEKF405SE.hex). Files are individually downloadable — no GitHub login required.

Development build for testing only. Use Full Chip Erase when flashing.

@github-actions

Copy link
Copy Markdown

RAM / Flash usage vs. base branch — commit 572035e

Target Flash Δ RAM Δ
MATEKF405 -16 B (-0.00%) ±0 B (±0.00%)
MATEKF722 ⚠️ +80 B (+0.02%) ±0 B (±0.00%)
MATEKF765 ±0 B (±0.00%) ±0 B (±0.00%)
MATEKH743 -16 B (-0.00%) ±0 B (±0.00%)

See RAM/flash optimization guide for techniques to reduce usage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant