diff --git a/platforms/posix/CMakeLists.txt b/platforms/posix/CMakeLists.txt index e5bf739d7f..6cd87f6240 100644 --- a/platforms/posix/CMakeLists.txt +++ b/platforms/posix/CMakeLists.txt @@ -35,6 +35,37 @@ else() ) endif() +if (BUILD_TESTING) + # Build mavlink fuzz tests. These run other modules and thus cannot be a functional/unit test + add_executable(mavlink_fuzz_tests EXCLUDE_FROM_ALL + src/px4/common/mavlink_fuzz_tests.cpp + apps.cpp + ) + set_target_properties(mavlink_fuzz_tests PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${PX4_BINARY_DIR}") + target_include_directories(mavlink_fuzz_tests PRIVATE SYSTEM "${CMAKE_BINARY_DIR}/mavlink") + add_dependencies(test_results mavlink_fuzz_tests) + link_fuzztest(mavlink_fuzz_tests) + target_link_libraries(mavlink_fuzz_tests + PRIVATE + ${module_libraries} + m + parameters + ) + target_compile_options(mavlink_fuzz_tests + PRIVATE + # There warnings come from within fuzztest + -Wno-float-equal + -Wno-sign-compare + -Wno-shadow + -Wno-extra + -Wno-non-template-friend + + # From mavlink + -Wno-cast-align + -Wno-address-of-packed-member + ) +endif() + target_link_libraries(px4 PRIVATE ${module_libraries} diff --git a/platforms/posix/src/px4/common/mavlink_fuzz_tests.cpp b/platforms/posix/src/px4/common/mavlink_fuzz_tests.cpp new file mode 100644 index 0000000000..13e221694d --- /dev/null +++ b/platforms/posix/src/px4/common/mavlink_fuzz_tests.cpp @@ -0,0 +1,146 @@ +/**************************************************************************** + * + * Copyright (c) 2025 PX4 Development Team. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name PX4 nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#define MODULE_NAME "mavlink_fuzz_tests" + +#include +#include + +#include +#include +#include +#include "px4_daemon/pxh.h" +#include + +#include +#include + +namespace px4 +{ +void init_once(); +} + +extern "C" const char *__asan_default_options() { return "detect_leaks=0"; } + +class MavlinkFuzzTest +{ +public: + MavlinkFuzzTest() + { + // This object will be reused for each fuzzing iteration call + px4::init_once(); + px4::init(0, nullptr, "px4"); + + px4_daemon::Pxh pxh; + pxh.process_line("param load", true); + pxh.process_line("dataman start", true); + pxh.process_line("load_mon start", true); + pxh.process_line("battery_simulator start", true); + pxh.process_line("tone_alarm start", true); + pxh.process_line("rc_update start", true); + pxh.process_line("sensors start", true); + pxh.process_line("commander start", true); + pxh.process_line("navigator start", true); + pxh.process_line("ekf2 start", true); + pxh.process_line("mc_att_control start", true); + pxh.process_line("mc_pos_control start", true); + pxh.process_line("land_detector start multicopter", true); + pxh.process_line("logger start", true); + pxh.process_line("mavlink start -x -o 14513 -u 14514 -r 4000000", true); + pxh.process_line("mavlink boot_complete", true); + + _socket_fd = socket(AF_INET, SOCK_DGRAM, 0); + + if (_socket_fd < 0) { + PX4_ERR("socket error: %s", strerror(errno)); + return; + } + + sockaddr_in addr {}; + + addr.sin_family = AF_INET; + + inet_pton(AF_INET, "0.0.0.0", &(addr.sin_addr)); + + addr.sin_port = htons(14513); + + if (bind(_socket_fd, reinterpret_cast(&addr), sizeof(addr)) != 0) { + PX4_ERR("bind error: %s", strerror(errno)); + close(_socket_fd); + return; + } + } + ~MavlinkFuzzTest() + { + close(_socket_fd); + } + + void run(std::vector data) + { + sendMavlink(data.data(), data.size()); + } +private: + void sendMavlink(const uint8_t *data, const size_t size) + { + mavlink_message_t message {}; + uint8_t buffer[MAVLINK_MAX_PACKET_LEN] {}; + + for (size_t i = 0; i < size; i += sizeof(message)) { + + const size_t copy_len = std::min(sizeof(message), size - i); + memcpy(reinterpret_cast(&message), data + i, copy_len); + + // Set a correct checksum as it is hard for the fuzzer to pass the correct checksum + mavlink_finalize_message_buffer(&message, message.sysid, message.compid, &_status, 0, message.len, + mavlink_get_crc_extra(&message)); + + const ssize_t buffer_len = mavlink_msg_to_send_buffer(buffer, &message); + + sockaddr_in dest_addr {}; + dest_addr.sin_family = AF_INET; + + inet_pton(AF_INET, "127.0.0.1", &dest_addr.sin_addr.s_addr); + dest_addr.sin_port = htons(14514); + + if (sendto(_socket_fd, buffer, buffer_len, 0, reinterpret_cast(&dest_addr), + sizeof(dest_addr)) != buffer_len) { + PX4_ERR("sendto error: %s", strerror(errno)); + } + } + } + + mavlink_status_t _status{}; + int _socket_fd{-1}; +}; + +FUZZ_TEST_F(MavlinkFuzzTest, run);