Skip to content

Guard MSP2_SENSOR_* receivers against short payloads - #11822

Open
sensei-hacker wants to merge 3 commits into
iNavFlight:maintenance-10.xfrom
sensei-hacker:msp-sensor-command-bounds-check
Open

Guard MSP2_SENSOR_* receivers against short payloads#11822
sensei-hacker wants to merge 3 commits into
iNavFlight:maintenance-10.xfrom
sensei-hacker:msp-sensor-command-bounds-check

Conversation

@sensei-hacker

Copy link
Copy Markdown
Member

Summary

mspProcessSensorCommand computed the incoming MSP payload size (dataSize) but discarded it (UNUSED(dataSize)), then dispatched to the six MSP2_SENSOR_* receiver functions (GPS, compass, baro, airspeed, opflow, rangefinder), each of which casts the raw payload pointer directly to a fixed-size packed struct and reads every field, with no check that the actual payload was that large. A short-but-CRC-valid MSP2_SENSOR_* frame (MSP v2's own CRC8 doesn't prevent a short frame, only a corrupted one) would let stale bytes from the FC's shared MSP input buffer be read as real sensor data and fed into navigation.

MSP2_SENSOR_HEADTRACKER, dispatched from the same function, was already correctly guarded — it receives dataSize explicitly and rejects any frame that isn't exactly the expected size. This makes the same robustness improvement to the other six sensor messages, closing the gap.

Changes

  • Thread dataSize from mspProcessSensorCommand (fc_msp.c) through to each of the six sensor receivers
  • Add if (dataSize != sizeof(expected_struct_t)) { return; } as the first statement in each receiver, before any field is read — matching the existing MSP2_SENSOR_HEADTRACKER pattern exactly
  • No behavior change for correctly-sized frames; a too-short or too-long frame is now silently dropped instead of partially/incorrectly applied

Testing

  • Built SITL successfully, no new compiler warnings
  • SITL test: sent a short MSP2_SENSOR_GPS frame (10 bytes instead of the 52-byte struct) with different values encoded in the truncated bytes — confirmed FC GPS state was unaffected (stayed at the prior valid frame's values), then confirmed a subsequent correctly-sized frame was still applied normally
  • SITL test: same check for MSP2_SENSOR_RANGEFINDER (5-byte struct, smallest of the six) with a 2-byte short frame — same result, rejected with no state change, valid frames still accepted

Code Review

Reviewed with the inav-code-review agent — approved, no critical or important issues found. One pre-existing (not introduced by this change) const-correctness inconsistency was noted in gps.h but is out of scope for this fix.

Related Issues

This was found while investigating #11672 (a different, already-resolved question about the six SET-handler bounds checks). It's a distinct code path — a general robustness fix, not a disclosed vulnerability.

Related to #11672

mspProcessSensorCommand computed the incoming payload size but
discarded it, letting GPS/compass/baro/airspeed/opflow/rangefinder
receivers cast an unchecked pointer to a fixed-size struct. A
short-but-CRC-valid frame let stale bytes from the shared MSP input
buffer be read as real sensor data. Thread dataSize through to each
receiver and reject any frame that isn't exactly the expected size,
matching the pattern already used by MSP2_SENSOR_HEADTRACKER.
@sensei-hacker sensei-hacker added this to the 9.1 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

Reject malformed MSP2 sensor payload sizes

🐞 Bug fix 🕐 20-40 Minutes

Grey Divider

AI Description

• Propagates MSP payload lengths to six fixed-structure sensor receivers.
• Rejects undersized and oversized frames before sensor fields or state are accessed.
• Preserves valid GPS, compass, barometer, airspeed, optical-flow, and rangefinder behavior.
Diagram

graph TD
    A["MSP v2 frame"] --> B["Sensor dispatcher"] --> C{"Exact size?"}
    C -- "Yes" --> D["Sensor receivers"] --> E["Navigation state"]
    C -- "No" --> F["Drop frame"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Validate centrally in the dispatcher
  • ➕ Keeps all command-to-payload-size rules in one location
  • ➕ Avoids repeating the same guard pattern in each receiver
  • ➖ Leaves receivers unsafe if called from another path
  • ➖ Couples the dispatcher to every receiver's packed message type
  • ➖ Makes receiver preconditions less explicit
2. Parse payloads through bounded readers
  • ➕ Avoids direct packed-structure casts
  • ➕ Can provide explicit field-level decoding and endianness handling
  • ➖ Substantially larger change for fixed-format local messages
  • ➖ Adds complexity without improving valid-frame behavior
  • ➖ Would require broader testing across all six sensor formats

Recommendation: Keep the receiver-boundary exact-size checks used by this PR. They match the established headtracker pattern, provide defense at the point of dereference, and minimize behavior change; centralized validation or bounded field parsing would be broader and less self-contained.

Files changed (13) +42 / -19

Bug fix (13) +42 / -19
barometer_msp.cReject invalid barometer payload sizes +5/-1

Reject invalid barometer payload sizes

• Extends the MSP barometer receiver with a payload-size argument and returns before casting or updating pressure, temperature, timestamps, and calibration state when the size is not exact.

src/main/drivers/barometer/barometer_msp.c

barometer_msp.hExpose barometer payload-size parameter +1/-1

Expose barometer payload-size parameter

• Updates the barometer MSP receiver declaration to accept the incoming payload length.

src/main/drivers/barometer/barometer_msp.h

compass_msp.cReject invalid compass payload sizes +5/-1

Reject invalid compass payload sizes

• Validates the compass message length before reading magnetic axes, applying alignment, or refreshing the update timestamp.

src/main/drivers/compass/compass_msp.c

compass_msp.hExpose compass payload-size parameter +1/-1

Expose compass payload-size parameter

• Updates the compass MSP receiver declaration to accept the incoming payload length.

src/main/drivers/compass/compass_msp.h

pitotmeter_msp.cReject invalid airspeed payload sizes +5/-1

Reject invalid airspeed payload sizes

• Checks the airspeed message length before reading differential pressure and temperature or updating the pitot timestamp.

src/main/drivers/pitotmeter/pitotmeter_msp.c

pitotmeter_msp.hExpose airspeed payload-size parameter +1/-1

Expose airspeed payload-size parameter

• Updates the pitotmeter MSP receiver declaration to accept the incoming payload length.

src/main/drivers/pitotmeter/pitotmeter_msp.h

fc_msp.cForward sensor payload sizes from MSP dispatch +6/-7

Forward sensor payload sizes from MSP dispatch

• Stops discarding the computed payload length and passes it to the rangefinder, optical-flow, GPS, compass, barometer, and airspeed receivers.

src/main/fc/fc_msp.c

gps.hExpose GPS payload-size parameter +1/-1

Expose GPS payload-size parameter

• Updates the GPS MSP receiver declaration to include the payload length while preserving its const buffer contract.

src/main/io/gps.h

gps_msp.cReject invalid GPS payload sizes +5/-1

Reject invalid GPS payload sizes

• Requires an exact GPS message size before decoding the packed payload and modifying the GPS solution used by navigation.

src/main/io/gps_msp.c

opflow.hExpose optical-flow payload-size parameter +1/-1

Expose optical-flow payload-size parameter

• Updates the optical-flow MSP receiver declaration to accept the incoming payload length.

src/main/io/opflow.h

opflow_msp.cReject invalid optical-flow payload sizes +5/-1

Reject invalid optical-flow payload sizes

• Validates message size before recording timing, motion rates, quality, or new-data availability.

src/main/io/opflow_msp.c

rangefinder.hExpose rangefinder payload-size parameter +1/-1

Expose rangefinder payload-size parameter

• Updates the rangefinder MSP receiver declaration to accept the incoming payload length.

src/main/io/rangefinder.h

rangefinder_msp.cReject invalid rangefinder payload sizes +5/-1

Reject invalid rangefinder payload sizes

• Requires the exact rangefinder structure size before converting distance and marking sensor data available.

src/main/io/rangefinder_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

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown

RAM / Flash usage vs. base branch — commit 1275317

No size baseline is available yet for this PR's base commit (no per-commit baseline has been published for it). This comment will show deltas once one exists — rebasing the PR refreshes its base commit.

Target Flash Δ RAM Δ
MATEKF405 701015 B (no baseline) 149532 B (no baseline)
MATEKF722 465571 B (no baseline) 125276 B (no baseline)
MATEKF765 733175 B (no baseline) 165468 B (no baseline)
MATEKH743 771319 B (no baseline) 167904 B (no baseline)

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

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown

Test firmware build ready — commit 1275317

Download firmware for PR #11822

247 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.

Under the forward-compat policy (MSP payloads only gain fields at the
end), a longer message from a newer sender must be accepted: apply the
known leading fields and ignore the trailing bytes. Relax all seven
receiver guards (GPS, compass, baro, airspeed, opflow, rangefinder,
headtracker) from exact-size `dataSize != sizeof(...)` to minimum-size
`dataSize < sizeof(...)`, so truncated frames are still rejected while
longer frames are accepted.
@sensei-hacker

Copy link
Copy Markdown
Member Author

Phase 4: lenient gates per forward-compat policy — implemented + SITL-verified.

Following the 2026-08-27 forward-compat policy (MSP payloads only ever gain fields at the END; a newer sender's longer message must be accepted by older firmware — apply known fields, ignore trailing bytes), all seven MSP2_SENSOR_* receiver guards move from exact-size to minimum-size:

Receiver Old guard New guard
GPS (mspSensorGpsDataMessage_t, 52B) dataSize != sizeof(...) dataSize < sizeof(...)
Compass (11B) != <
Baro (11B) != <
Airspeed (11B) != <
Opflow (9B) != <
Rangefinder (5B) != <
HeadTracker (headtrackerMspMessage_t) != < (same reference pattern, updated in this PR)

Truncated frames are still rejected outright (no partial state mutation), while longer frames are accepted — the receiver casts the buffer to the fixed struct and reads only the leading sizeof bytes, so trailing bytes are never touched. This also keeps the original fix's purpose (short-but-CRC-valid frames can no longer read stale buffer bytes past the payload).

SITL verification (verify_sensor_command_bounds_check.py, all checks pass):

  • GPS: 10-byte short frame rejected (state unchanged from baseline), 52-byte exact frame applied, 56-byte longer frame accepted with known fields (fixType/numSat/lat/lon) applied and 4 trailing bytes ignored.
  • Rangefinder: 2-byte short frame rejected (altitude unchanged), 5-byte exact frame applied, 8-byte longer frame accepted with distance applied and 3 trailing bytes ignored.
  • FC responsive throughout; valid frames accepted after each rejection.

Commit: 4621536 on msp-sensor-command-bounds-check.

@sensei-hacker
sensei-hacker changed the base branch from release/9.1 to maintenance-10.x August 29, 2026 22:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant