Report: PX4 numeric conversion and truncation issues in MAVLink command handling

This report covers two MAVLink command-handling cases where PX4 accepts non-integer or negative numeric inputs and then converts them into different command semantics inside PX4.

Tested environment:

PX4 SITL: sihsim_quadx
PX4 version: v1.14.0-6-g33aca30c93-dirty
PX4 git hash: 33aca30c93863efb0a73991c391756876663bb72

A. Mission-item numeric narrowing

A1. MAV_CMD_DO_JUMP: negative repeat count is accepted and stored as 65535

MAV_CMD_DO_JUMP uses:

param1 = target mission index
param2 = repeat count

Reproduction:

  • Clear the current mission.
  • Upload a mission item with command=MAV_CMD_DO_JUMP.
  • Use param1=0 and param2=-1.
  • Download the mission back from PX4.

Observed upload result:

MISSION_ACK: MAV_MISSION_ACCEPTED

Observed downloaded mission item:

MISSION_ITEM_INT seq=0 command=177 frame=2 current=1 autocontinue=1
p1=0.0 p2=65535.0

PX4 accepted the negative repeat count and stored it as 65535.

I also tested a runnable mission containing:

seq 0: MAV_CMD_NAV_TAKEOFF
seq 1: MAV_CMD_DO_JUMP, param1=1, param2=-1
seq 2: MAV_CMD_NAV_WAYPOINT

The upload was accepted, and downloading the mission again showed:

seq=1 command=177 p1=1.0 p2=65535.0

During execution, PX4 repeatedly logged:

WARN [navigator] DO JUMP is cycling, giving up.

Expected behavior: negative repeat counts should be rejected during mission upload.

Actual behavior: a negative repeat count is accepted and converted into a large unsigned repeat count.

Relevant source in the tested PX4 version:

// mavlink_mission.cpp
case MAV_CMD_DO_JUMP:
    mission_item->nav_cmd = NAV_CMD_DO_JUMP;
    mission_item->do_jump_mission_index = mavlink_mission_item->param1;
    mission_item->do_jump_current_count = 0;
    mission_item->do_jump_repeat_count = mavlink_mission_item->param2;
    break;

do_jump_repeat_count is an unsigned mission field:

// navigation.h
uint16_t do_jump_repeat_count;

When PX4 serializes the mission item back to MAVLink, the stored value is exposed as the repeat count:

// mavlink_mission.cpp
mavlink_mission_item->param2 = mission_item->do_jump_repeat_count;

Navigator then compares the current jump counter against that stored repeat count:

if ((mission_item_tmp.do_jump_current_count < mission_item_tmp.do_jump_repeat_count) && execute_jumps) {
    ...
    *mission_index_ptr = mission_item_tmp.do_jump_mission_index;
}

Impact:

  • A malformed mission can be accepted even though the repeat count is outside the valid domain.
  • The uploaded mission no longer has the same semantics as the MAVLink mission item sent by the uploader.
  • A small negative input can become a very large repeat count in persistent mission storage.

B. Command parameter truncation

B1. MAV_CMD_PREFLIGHT_STORAGE: fractional action selectors are accepted and truncated to integer storage actions

MAV_CMD_PREFLIGHT_STORAGE uses param1 as a storage action selector. PX4 accepts fractional values and truncates them with a C-style integer cast before deciding which storage action to run.

Reproduction:

Send COMMAND_LONG for MAV_CMD_PREFLIGHT_STORAGE with fractional param1 values:

param1 = 0.5
param1 = 0.9
param1 = 1.1
param1 = 1.9
param1 = 2.1

Observed results:

param1=0.5 -> COMMAND_ACK: MAV_RESULT_ACCEPTED
param1=0.9 -> COMMAND_ACK: MAV_RESULT_ACCEPTED
param1=1.1 -> COMMAND_ACK: MAV_RESULT_ACCEPTED
param1=1.9 -> COMMAND_ACK: MAV_RESULT_ACCEPTED
param1=2.1 -> COMMAND_ACK: MAV_RESULT_ACCEPTED

PX4 shell output after the test showed storage/parameter activity, including parameter BSON import messages and calibration-related parameter changes.

Expected behavior: fractional action selectors should be rejected because they do not exactly identify a valid storage action.

Actual behavior: fractional values are accepted and truncated to integer action IDs.

Relevant source in the tested PX4 version:

// Commander.cpp
case vehicle_command_s::VEHICLE_CMD_PREFLIGHT_STORAGE: {
    ...
    if (((int)(cmd.param1)) == 0) {
        answer_command(cmd, vehicle_command_ack_s::VEHICLE_CMD_RESULT_ACCEPTED);
        _worker_thread.startTask(WorkerThread::Request::ParamLoadDefault);

    } else if (((int)(cmd.param1)) == 1) {
        answer_command(cmd, vehicle_command_ack_s::VEHICLE_CMD_RESULT_ACCEPTED);
        _worker_thread.startTask(WorkerThread::Request::ParamSaveDefault);

    } else if (((int)(cmd.param1)) == 2) {
        answer_command(cmd, vehicle_command_ack_s::VEHICLE_CMD_RESULT_ACCEPTED);
        _worker_thread.startTask(WorkerThread::Request::ParamResetAllConfig);

Because cmd.param1 is cast to int, examples such as 0.9, 1.9, and 2.1 are accepted as action IDs 0, 1, and 2.

Impact:

  • A malformed storage command can trigger a valid storage action after truncation.
  • The acknowledged command semantics differ from the exact MAVLink parameter value sent by the uploader.
  • This is especially sensitive because the command family controls parameter loading, saving, and reset operations.

Suggested fixes

  • Validate that action selector parameters are finite integers before dispatch.
  • Reject negative or fractional DO_JUMP repeat counts during mission upload.
  • Reject out-of-range DO_JUMP repeat counts before writing the mission item to dataman.
  • Avoid implicit float-to-integer casts for MAVLink command action selectors unless the accepted rounding/truncation behavior is explicitly intended and documented.