Skip to content

Heap Use-After-Free in MavlinkShell::available() via SERIAL_CONTROL Race Condition

Moderate
mrpollo published GHSA-j5w2-w79c-mqrw Mar 13, 2026

Package

PX4/PX4-Autopilot (Other)

Affected versions

< 1.17.0-rc1

Patched versions

1.17.0-rc1

Description

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.

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Adjacent
Attack complexity
High
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H

CVE ID

CVE-2026-32724

Weaknesses

Use After Free

The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory belongs to the code that operates on the new pointer. Learn more on MITRE.

Credits