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=0andparam2=-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_JUMPrepeat counts during mission upload. - Reject out-of-range
DO_JUMPrepeat 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.