Skip to content

MSP ESC 4-way passthrough can disable motor outputs without checking the armed state #11777

Description

@jFriedli

Current Behavior

The MSP passthrough handler treats a zero-length request as the legacy ESC 4-way mode:

const unsigned int dataSize = sbufBytesRemaining(src);

if (dataSize == 0) {
    mspPassthroughMode = MSP_PASSTHROUGH_ESC_4WAY;
}

For MSP_PASSTHROUGH_ESC_4WAY, the handler immediately calls:

sbufWriteU8(dst, esc4wayInit());

No armed-state check is performed in this path.

esc4wayInit() begins by disabling the motor PWM outputs:

uint8_t esc4wayInit(void)
{
    pwmDisableMotors();
    ...
}

Affected source in the tested commit:

  • https://github.com/iNavFlight/inav/blob/c5c593d71d33c8e284bf9cd34381588fda7a98c8/src/main/fc/fc_msp.c#L2547-L2610
  • https://github.com/iNavFlight/inav/blob/c5c593d71d33c8e284bf9cd34381588fda7a98c8/src/main/io/serial_4way.c#L2193-L2224

MSP-over-telemetry commands are passed into the normal FC MSP dispatcher:

  • https://github.com/iNavFlight/inav/blob/c5c593d71d33c8e284bf9cd34381588fda7a98c8/src/main/telemetry/msp_shared.c#L840-L865

Security impact

This is primarily a safety-impacting denial-of-service / control-plane vulnerability.

An accepted MSP passthrough command can reach esc4wayInit() without first checking whether the flight controller is armed. esc4wayInit() calls pwmDisableMotors().

If an attacker or compromised MSP-capable peer can issue this command while the aircraft is armed, the command path can disable motor outputs during an armed state. On a flying multirotor, that can cause immediate loss of propulsion and loss of the aircraft.

This is security-relevant even if the MSP peer is normally considered trusted: a security boundary exists wherever an external peer can issue protocol commands that affect flight-critical outputs, and the command lacks the state guard used by other dangerous operations.

Attacker preconditions

The attacker or peer must be able to submit accepted MSP commands through the configured MSP transport. This report does not claim that an arbitrary nearby radio user can do so on every INAV installation.

What is proven

  • The zero-length MSP_SET_PASSTHROUGH path reaches ESC 4-way initialization.
  • The production esc4wayInit() implementation calls pwmDisableMotors().
  • No armed-state check is present before that call in the tested path.
  • A host-side control-flow harness reproduces the armed → motor-disabled state transition.

What is not proven

  • An in-flight test.
  • A physical FC test with motors connected.
  • The exact behavior of every supported target/ESC configuration.

Steps to Reproduce

I verified the production call chain in INAV 9.1 source and reproduced the missing state gate in a small host-side control-flow harness.

This is not a test performed with motors connected, a flying aircraft, or a physical FC.

Save as poc_inav_armed_passthrough.c:

#include <stdbool.h>
#include <stdio.h>

static bool armed = true;
static bool motors_disabled = false;

static unsigned esc4wayInit(void)
{
    motors_disabled = true; /* models pwmDisableMotors() */
    return 4;
}

static void msp_set_passthrough(unsigned payload_size)
{
    enum { MSP_PASSTHROUGH_ESC_4WAY = 0 } mode;

    if (payload_size == 0) {
        mode = MSP_PASSTHROUGH_ESC_4WAY;
    } else {
        return;
    }

    if (mode == MSP_PASSTHROUGH_ESC_4WAY) {
        (void)esc4wayInit();
    }
}

int main(void)
{
    printf("armed before=%s motors_disabled before=%s\n",
           armed ? "true" : "false",
           motors_disabled ? "true" : "false");

    msp_set_passthrough(0);

    printf("armed after=%s motors_disabled after=%s\n",
           armed ? "true" : "false",
           motors_disabled ? "true" : "false");

    if (armed && motors_disabled) {
        puts("REPRODUCED: passthrough reached motor-disable path while armed");
        return 0;
    }

    return 1;
}

Compile and run:

gcc -O0 -g poc_inav_armed_passthrough.c -o poc_inav_armed_passthrough
./poc_inav_armed_passthrough

Observed locally:

armed before=true motors_disabled before=false
armed after=true motors_disabled after=true
REPRODUCED: passthrough reached motor-disable path while armed

The tested INAV tree was:

commit: c5c593d71d33c8e284bf9cd34381588fda7a98c8
date:   2026-07-18 22:18:56 -0500
subject: Merge pull request #11728 from iNavFlight/release/9.1

Expected behavior

Entering ESC programming/passthrough mode should be rejected while the flight controller is armed.

A command that disables or reconfigures motor outputs should require an explicit safe/disarmed state before any motor-output state is modified.

Suggested solution(s)

Reject ESC 4-way passthrough when armed before calling esc4wayInit(), for example at the MSP command-handler level.

The state check should occur before pwmDisableMotors() or any GPIO reconfiguration.

Additional context

The safety impact is the main concern: the production call chain allows the ESC 4-way initialization routine, which disables motor outputs, to be reached without an armed-state check.

The security reachability depends on whether a peer can send accepted MSP commands through the configured transport. INAV's shared MSP-over-telemetry path calls the normal MSP command dispatcher, but individual radio/telemetry links may impose their own trust or authentication boundary.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions