Summary
When fuzzing SERIAL_CONTROL (MAVLink ID 126) messages in PX4 SITL with ASan enabled, a heap-use-after-free is detected in the MavlinkShell::available() function. The issue is caused by a race condition between the receiver thread (which handles shell creation/destruction) and the sender thread (which polls the shell for available output).
Environment
PX4 version:confirmed with main and release v1.15.4 and other..
Build config: SITL, compiled with AddressSanitizer
Host OS: Ubuntu 22.04 LTS
Simulator: PX4 SITL
GCS: QGroundControl (connected during testing)
Detail
The issue stems from unsynchronized access to the _mavlink_shell pointer shared between:
MavlinkReceiver::handle_message_serial_control() — which may call get_shell() and close_shell(), and
Mavlink::task_main() — which periodically checks _mavlink_shell->available() to send serial output back via MAVLink.
When one thread deletes the _mavlink_shell instance via close_shell() (on receiving a SERIAL_CONTROL without the RESPOND flag), the other thread may still dereference the freed pointer during its polling loop.
This results in a classic use-after-free triggered under specific timing conditions.
ASAN
=================================================================
==909005==ERROR: AddressSanitizer: heap-use-after-free on address 0x6030005e6608 at pc 0x589dbc113c26 bp 0x77c6a01f4630 sp 0x77c6a01f4620
INFO [mavlink] Starting mavlink shell
READ of size 4 at 0x6030005e6608 thread T59
#0 0x589dbc113c25 in MavlinkShell::available() ../../src/modules/mavlink/mavlink_shell.cpp:229
#1 0x589dbc078914 in Mavlink::task_main(int, char**) ../../src/modules/mavlink/mavlink_main.cpp:2379
#2 0x589dbc07fd8b in Mavlink::start_helper(int, char**) ../../src/modules/mavlink/mavlink_main.cpp:2864
#3 0x589dbc66e1bc in entry_adapter ../../platforms/posix/src/px4/common/tasks.cpp:98
#4 0x77c6a8894ac2 in start_thread nptl/pthread_create.c:442
#5 0x77c6a892684f (/usr/lib/x86_64-linux-gnu/libc.so.6+0x12684f)
0x6030005e6608 is located 8 bytes inside of 24-byte region [0x6030005e6600,0x6030005e6618)
freed by thread T60 here:
#0 0x77c6a98b724f in operator delete(void*, unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:172
#1 0x589dbc069a76 in Mavlink::close_shell() ../../src/modules/mavlink/mavlink_main.cpp:1291
previously allocated by thread T60 here:
#0 0x77c6a98b61e7 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:99
#1 0x589dbc06985d in Mavlink::get_shell() ../../src/modules/mavlink/mavlink_main.cpp:1266
#2 0x77c6a84a9c6f ()
Thread T59 created by T58 here:
#0 0x77c6a9858685 in __interceptor_pthread_create ../../../../src/libsanitizer/asan/asan_interceptors.cpp:216
#1 0x589dbc66dda4 in px4_task_spawn_cmd ../../platforms/posix/src/px4/common/tasks.cpp:252
Thread T58 created by T1 here:
#0 0x77c6a9858685 in __interceptor_pthread_create ../../../../src/libsanitizer/asan/asan_interceptors.cpp:216
#1 0x589dbc686ac7 in px4_daemon::Server::_server_main() ../../platforms/posix/src/px4/common/px4_daemon/server.cpp:185
Thread T1 created by T0 here:
#0 0x77c6a9858685 in __interceptor_pthread_create ../../../../src/libsanitizer/asan/asan_interceptors.cpp:216
#1 0x589dbc68298a in px4_daemon::Server::start() ../../platforms/posix/src/px4/common/px4_daemon/server.cpp:106
#2 0x3055e3 ()
Thread T60 created by T59 here:
#0 0x77c6a9858685 in __interceptor_pthread_create ../../../../src/libsanitizer/asan/asan_interceptors.cpp:216
#1 0x589dbc109b75 in MavlinkReceiver::start() ../../src/modules/mavlink/mavlink_receiver.cpp:3494
SUMMARY: AddressSanitizer: heap-use-after-free ../../src/modules/mavlink/mavlink_shell.cpp:229 in MavlinkShell::available()
Shadow bytes around the buggy address:
0x0c06800b4c70: fa fa fd fd fd fd fa fa fd fd fd fa fa fa fd fd
0x0c06800b4c80: fd fa fa fa fd fd fd fa fa fa fd fd fd fa fa fa
0x0c06800b4c90: fd fd fd fa fa fa fd fd fd fa fa fa fd fd fd fd
0x0c06800b4ca0: fa fa fd fd fd fa fa fa fd fd fd fd fa fa fd fd
0x0c06800b4cb0: fd fd fa fa fd fd fd fa fa fa fd fd fd fa fa fa
=>0x0c06800b4cc0: fd[fd]fd fa fa fa 00 00 00 fa fa fa fa fa fa fa
0x0c06800b4cd0: fa fa fa fa fa fa fa fa fd fd fd fd fa fa fd fd
0x0c06800b4ce0: fd fa fa fa fd fd fd fd fa fa fd fd fd fd fa fa
0x0c06800b4cf0: fd fd fd fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c06800b4d00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c06800b4d10: fa fa fa fa fd fd fd fd fa fa fd fd fd fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==909005==ABORTING
PoC
$ make px4_sitl jmavsim PX4_ASAN=1
Launch PX4 SITL compiled with AddressSanitizer.
Connect QGroundControl or simulate MAVLink heartbeat.
Run this minimal PoC
https://drive.google.com/file/d/1P-P8uCMlCO0tV8WZosIrh4AYsj_2ZDDj/view?usp=sharing
Due to the nature of the race condition between MAVLink receiver and sender threads, this vulnerability may not trigger deterministically on every run. However, in our testing, the use-after-free can be reliably reproduced within a few attempts, especially when running the provided PoC multiple times in succession. This is consistent with typical race condition behavior and reinforces the need for proper synchronization in the affected code paths.
Impact
This vulnerability is classified as a heap use-after-free caused by a race condition between the MAVLink receiver and telemetry sender threads in PX4.
The issue is remotely triggerable via MAVLink SERIAL_CONTROL messages (ID 126), which can be sent by an external ground station or automated script.
It leads to undefined behavior, typically resulting in crashes, memory corruption, or non-deterministic behavior in the flight controller.
-While we have not demonstrated full remote code execution (RCE), use-after-free bugs are often considered critical due to their potential exploitability in other contexts or under different memory conditions.
Summary
When fuzzing SERIAL_CONTROL (MAVLink ID 126) messages in PX4 SITL with ASan enabled, a heap-use-after-free is detected in the MavlinkShell::available() function. The issue is caused by a race condition between the receiver thread (which handles shell creation/destruction) and the sender thread (which polls the shell for available output).
Environment
PX4 version:confirmed with main and release v1.15.4 and other..
Build config: SITL, compiled with AddressSanitizer
Host OS: Ubuntu 22.04 LTS
Simulator: PX4 SITL
GCS: QGroundControl (connected during testing)
Detail
The issue stems from unsynchronized access to the _mavlink_shell pointer shared between:
MavlinkReceiver::handle_message_serial_control() — which may call get_shell() and close_shell(), and
Mavlink::task_main() — which periodically checks _mavlink_shell->available() to send serial output back via MAVLink.
When one thread deletes the _mavlink_shell instance via close_shell() (on receiving a SERIAL_CONTROL without the RESPOND flag), the other thread may still dereference the freed pointer during its polling loop.
This results in a classic use-after-free triggered under specific timing conditions.
ASAN
=================================================================
==909005==ERROR: AddressSanitizer: heap-use-after-free on address 0x6030005e6608 at pc 0x589dbc113c26 bp 0x77c6a01f4630 sp 0x77c6a01f4620
INFO [mavlink] Starting mavlink shell
READ of size 4 at 0x6030005e6608 thread T59
#0 0x589dbc113c25 in MavlinkShell::available() ../../src/modules/mavlink/mavlink_shell.cpp:229
#1 0x589dbc078914 in Mavlink::task_main(int, char**) ../../src/modules/mavlink/mavlink_main.cpp:2379
#2 0x589dbc07fd8b in Mavlink::start_helper(int, char**) ../../src/modules/mavlink/mavlink_main.cpp:2864
#3 0x589dbc66e1bc in entry_adapter ../../platforms/posix/src/px4/common/tasks.cpp:98
#4 0x77c6a8894ac2 in start_thread nptl/pthread_create.c:442
#5 0x77c6a892684f (/usr/lib/x86_64-linux-gnu/libc.so.6+0x12684f)
0x6030005e6608 is located 8 bytes inside of 24-byte region [0x6030005e6600,0x6030005e6618)
freed by thread T60 here:
#0 0x77c6a98b724f in operator delete(void*, unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:172
#1 0x589dbc069a76 in Mavlink::close_shell() ../../src/modules/mavlink/mavlink_main.cpp:1291
previously allocated by thread T60 here:
#0 0x77c6a98b61e7 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:99
#1 0x589dbc06985d in Mavlink::get_shell() ../../src/modules/mavlink/mavlink_main.cpp:1266
#2 0x77c6a84a9c6f ()
Thread T59 created by T58 here:
#0 0x77c6a9858685 in __interceptor_pthread_create ../../../../src/libsanitizer/asan/asan_interceptors.cpp:216
#1 0x589dbc66dda4 in px4_task_spawn_cmd ../../platforms/posix/src/px4/common/tasks.cpp:252
Thread T58 created by T1 here:
#0 0x77c6a9858685 in __interceptor_pthread_create ../../../../src/libsanitizer/asan/asan_interceptors.cpp:216
#1 0x589dbc686ac7 in px4_daemon::Server::_server_main() ../../platforms/posix/src/px4/common/px4_daemon/server.cpp:185
Thread T1 created by T0 here:
#0 0x77c6a9858685 in __interceptor_pthread_create ../../../../src/libsanitizer/asan/asan_interceptors.cpp:216
#1 0x589dbc68298a in px4_daemon::Server::start() ../../platforms/posix/src/px4/common/px4_daemon/server.cpp:106
#2 0x3055e3 ()
Thread T60 created by T59 here:
#0 0x77c6a9858685 in __interceptor_pthread_create ../../../../src/libsanitizer/asan/asan_interceptors.cpp:216
#1 0x589dbc109b75 in MavlinkReceiver::start() ../../src/modules/mavlink/mavlink_receiver.cpp:3494
SUMMARY: AddressSanitizer: heap-use-after-free ../../src/modules/mavlink/mavlink_shell.cpp:229 in MavlinkShell::available()
Shadow bytes around the buggy address:
0x0c06800b4c70: fa fa fd fd fd fd fa fa fd fd fd fa fa fa fd fd
0x0c06800b4c80: fd fa fa fa fd fd fd fa fa fa fd fd fd fa fa fa
0x0c06800b4c90: fd fd fd fa fa fa fd fd fd fa fa fa fd fd fd fd
0x0c06800b4ca0: fa fa fd fd fd fa fa fa fd fd fd fd fa fa fd fd
0x0c06800b4cb0: fd fd fa fa fd fd fd fa fa fa fd fd fd fa fa fa
=>0x0c06800b4cc0: fd[fd]fd fa fa fa 00 00 00 fa fa fa fa fa fa fa
0x0c06800b4cd0: fa fa fa fa fa fa fa fa fd fd fd fd fa fa fd fd
0x0c06800b4ce0: fd fa fa fa fd fd fd fd fa fa fd fd fd fd fa fa
0x0c06800b4cf0: fd fd fd fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c06800b4d00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c06800b4d10: fa fa fa fa fd fd fd fd fa fa fd fd fd fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==909005==ABORTING
PoC
$ make px4_sitl jmavsim PX4_ASAN=1
Launch PX4 SITL compiled with AddressSanitizer.
Connect QGroundControl or simulate MAVLink heartbeat.
Run this minimal PoC
https://drive.google.com/file/d/1P-P8uCMlCO0tV8WZosIrh4AYsj_2ZDDj/view?usp=sharing
Due to the nature of the race condition between MAVLink receiver and sender threads, this vulnerability may not trigger deterministically on every run. However, in our testing, the use-after-free can be reliably reproduced within a few attempts, especially when running the provided PoC multiple times in succession. This is consistent with typical race condition behavior and reinforces the need for proper synchronization in the affected code paths.
Impact
This vulnerability is classified as a heap use-after-free caused by a race condition between the MAVLink receiver and telemetry sender threads in PX4.
The issue is remotely triggerable via MAVLink SERIAL_CONTROL messages (ID 126), which can be sent by an external ground station or automated script.
It leads to undefined behavior, typically resulting in crashes, memory corruption, or non-deterministic behavior in the flight controller.
-While we have not demonstrated full remote code execution (RCE), use-after-free bugs are often considered critical due to their potential exploitability in other contexts or under different memory conditions.