Security
Headlines
HeadlinesLatestCVEs

Headline

CVE-2023-47625: [Bug Report] Vulnerable Bug Found in CrsfParser_TryParseCrsfPacket which can trigger Global Buffer Overflow

PX4 autopilot is a flight control solution for drones. In affected versions a global buffer overflow vulnerability exists in the CrsfParser_TryParseCrsfPacket function in /src/drivers/rc/crsf_rc/CrsfParser.cpp:298 due to the invalid size check. A malicious user may create an RC packet remotely and that packet goes into the device where the _rcs_buf reads. The global buffer overflow vulnerability will be triggered and the drone can behave unexpectedly. This issue has been addressed in version 1.14.0. Users are advised to upgrade. There are no known workarounds for this vulnerability.

CVE
#csrf#vulnerability#ubuntu#linux#buffer_overflow

Summary

We identified a global buffer overflow vulnerability in the CrsfParser_TryParseCrsfPacket function in /src/drivers/rc/crsf_rc/CrsfParser.cpp:298 due to the invalid size check.

We investigated by the following process

  1. Enable the crsf_rc driver
  2. Write the data (i.e., packet) to _rcs_buf used in the crsf_rc driver on a device
  3. Global buffer overflow vulnerability in the CrsfParser_TryParseCrsfPacket function will be triggered when the CrsfParser_TryParseCrsfPacket function parses this data.

The following packet can trigger the vulnerability as mentioned above.

packet[0] = 0xc8; // MAGIC NUMBER which informs rc packet start packet[1] = 62; // size of the packet packet[2] = 100; // packet type ( random, not the 0x16, 0x14 )

Detailed Root Cause

We first assumed that we can write the arbitrary data into _rcs_buf using the following code.

  • /src/drivers/rc/crsf_rc/CrsfRc.cpp #190 line

int new_bytes = ::read(_rc_fd, &_rcs_buf[0], RC_MAX_BUFFER_SIZE);

After that, CrsfParser_TryParseCrsfPacket will be called and parse the information of the rc packet in _rcs_buf.

  • The CrsfParser_TryParseCrsfPacket function checks the following conditions
  1. The first byte of the packet is same with CRSF_HEADER (0xc8). (CrsfParser.cpp:256)

case PARSER_STATE_HEADER: if (QueueBuffer_Get(&rx_queue, &working_byte)) { if (working_byte == CRSF_HEADER) { //…omitted…

  1. Whether the type of the packet is known or not.
    • If the packet’s type is not 0x16 or 0x14, then the working_segment_size sets packet_size(our input) + PACKET_SIZE_TYPE(2);
  2. The working_segment_size should be less or equal than CRSF_MAX_PACKET_LEN which is 64. (if condition in CrsfParser.cpp:298)
    • In that case, if we send the packet which sets size as 62, then working_segment_size will be 64, and this corresponds to the if condition (working_segment_size <= CRSF_MAX_PACKET_LEN). Because the previous condition added 2 with the size of the packet.
    • It makes to pass this if condition, and working_index will set as 2 consequently.

case PARSER_STATE_SIZE_TYPE: //PX4_INFO(“PARSER_STATE_SIZE_TYPE\n”); QueueBuffer_Peek(&rx_queue, working_index++, &packet_size); //working_index = 1 QueueBuffer_Peek(&rx_queue, working_index++, &packet_type); //working_index = 2

        working\_descriptor \= FindCrsfDescriptor((enum CRSF\_PACKET\_TYPE)packet\_type);

        // If we know what this packet is...
        if (working\_descriptor != NULL) {
            //.. the packet's type was 0x16 or 0x14
            //..omitted
            }
        } else {
            // We don't know what this packet is, so we'll let the parser continue
            // just so that we can dequeue it in one shot
            working\_segment\_size \= packet\_size + PACKET\_SIZE\_TYPE\_SIZE; //PACKET\_SIZE\_TYPE\_SIZE : 2

            if (working\_segment\_size \> CRSF\_MAX\_PACKET\_LEN) {
                parser\_statistics\->invalid\_unknown\_packet\_sizes++;
                parser\_state \= PARSER\_STATE\_HEADER;
                working\_segment\_size \= HEADER\_SIZE;
                working\_index \= 0;
                buffer\_count \= QueueBuffer\_Count(&rx\_queue);
                continue;
            }
        }

        parser\_state \= PARSER\_STATE\_PAYLOAD;
        break;
  • Next, in the PARSER_STATE_HEADER part, working_index += working_segment_size is conducted. For our case, working_index is 2 because of the previous parsing part, working_idex will be 2 + 64 = 66.

case PARSER_STATE_PAYLOAD: working_index += working_segment_size; working_segment_size = CRC_SIZE; parser_state = PARSER_STATE_CRC; break;

  • The QueueBuffer_PeekBuffer function will be called with the 4th parameter represents size as 67 (because CRC_SIZE is 1)

case PARSER_STATE_CRC: // Fetch the suspected packet as a contingous block of memory QueueBuffer_PeekBuffer(&rx_queue, 0, process_buffer, working_index + CRC_SIZE);

bool QueueBuffer_PeekBuffer(const QueueBuffer_t *q, const uint32_t index, uint8_t *buffer, const uint32_t size) { uint32_t copy_start;

//...omitted

// Check if we can do this in a single shot
if (copy\_start + size <= q\->buffer\_size) {
    memcpy((void \*)buffer, (void \*)(q\->buffer + copy\_start), size); //this will be called

} 
  • Eventually, the data is copied to process_buffer like this (Our case for QueueBuffer.cpp:153)

memcpy(process_buffer, data, 67);

  • But the initialized size of process_buffer is 64

    #define CRSF_MAX_PACKET_LEN 64 static uint8_t process_buffer[CRSF_MAX_PACKET_LEN];

Therefore, the Global buffer overflow will be triggered.

PoC ( Reproduce Method )

We want to reproduce this crash using the actual rc packet, but we just triggered this crash in sitl because we don’t have the actual device yet. So, we’ve tested under the situation we can write the arbitrary data in the device using the crsf_rc driver to receive packets.

So, we suggest the following process to reproduce this packet.

  1. We set the csrf_rc driver in sitl by modifying the configure of default.px4board.

    CONFIG_DRIVERS_RC_CRSF_RC=y

  2. We set the _rcs_buf buffer (in drivers/crsf_rc/CrsfRc.cpp line 190) as below

    • Assuming that the buffer gets the data.

    int new_bytes = ::read(_rc_fd, &_rcs_buf[0], RC_MAX_BUFFER_SIZE); _rcs_buf[0] = 0xc8; // MAGIC NUMBER which informs rc packet start _rcs_buf[1] = 62; // size of the packet ( which is vulnerable ) _rcs_buf[2] = 100; // packet type ( random, not the 0x16, 0x14 ) new_bytes = 60 // informs the packet is received

  3. We run the crsf_rc driver like this

    pxh> crsf_rc start -d <device>

    • For example,

    pxh> crsf_rc start -d /dev/ttyS3

Address Sanitizer Log

================================================================= ==5090==ERROR: AddressSanitizer: global-buffer-overflow on address 0x563750d9c500 at pc 0x7f15a823a2c3 bp 0x7f15a4606220 sp 0x7f15a46059c8 WRITE of size 67 at 0x563750d9c500 thread T140 ERROR [simulator_mavlink] poll timeout 0, 22 #0 0x7f15a823a2c2 in __interceptor_memcpy …/…/…/…/src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:827 #1 0x56375022476a in memcpy /usr/include/x86_64-linux-gnu/bits/string_fortified.h:29 #2 0x56375022476a in QueueBuffer_PeekBuffer(QueueBuffer_t const*, unsigned int, unsigned char*, unsigned int) /home/ubuntu/drone/PX4-Autopilot/src/drivers/rc/crsf_rc/QueueBuffer.cpp:156 #3 0x563750223952 in CrsfParser_TryParseCrsfPacket(CrsfPacket_t*, CrsfParserStatistics_t*) /home/ubuntu/drone/PX4-Autopilot/src/drivers/rc/crsf_rc/CrsfParser.cpp:330 #4 0x56375022126d in CrsfRc::Run() /home/ubuntu/drone/PX4-Autopilot/src/drivers/rc/crsf_rc/CrsfRc.cpp:211 #5 0x563750a44b00 in px4::WorkQueue::Run() /home/ubuntu/drone/PX4-Autopilot/platforms/common/px4_work_queue/WorkQueue.cpp:188 #6 0x563750a459bc in WorkQueueRunner /home/ubuntu/drone/PX4-Autopilot/platforms/common/px4_work_queue/WorkQueueManager.cpp:238 #7 0x7f15a7294b42 in start_thread nptl/pthread_create.c:442 #8 0x7f15a73269ff (/lib/x86_64-linux-gnu/libc.so.6+0x1269ff)

0x563750d9c500 is located 32 bytes to the left of global variable ‘rx_queue_buffer’ defined in ‘/home/ubuntu/drone/PX4-Autopilot/src/drivers/rc/crsf_rc/CrsfParser.cpp:138:16’ (0x563750d9c520) of size 200 0x563750d9c500 is located 0 bytes to the right of global variable ‘process_buffer’ defined in ‘/home/ubuntu/drone/PX4-Autopilot/src/drivers/rc/crsf_rc/CrsfParser.cpp:139:16’ (0x563750d9c4c0) of size 64 SUMMARY: AddressSanitizer: global-buffer-overflow …/…/…/…/src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:827 in __interceptor_memcpy Shadow bytes around the buggy address: 0x0ac76a1ab850: f9 f9 f9 f9 00 00 00 00 02 f9 f9 f9 f9 f9 f9 f9 0x0ac76a1ab860: 00 00 00 00 00 f9 f9 f9 f9 f9 f9 f9 00 00 00 00 0x0ac76a1ab870: 00 f9 f9 f9 f9 f9 f9 f9 01 f9 f9 f9 f9 f9 f9 f9 0x0ac76a1ab880: 00 00 00 00 00 f9 f9 f9 f9 f9 f9 f9 00 00 00 00 0x0ac76a1ab890: 00 f9 f9 f9 f9 f9 f9 f9 00 00 00 00 00 00 00 00 =>0x0ac76a1ab8a0:[f9]f9 f9 f9 00 00 00 00 00 00 00 00 00 00 00 00 0x0ac76a1ab8b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 f9 f9 f9 0x0ac76a1ab8c0: f9 f9 f9 f9 00 00 00 f9 f9 f9 f9 f9 04 f9 f9 f9 0x0ac76a1ab8d0: f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9 00 00 00 00 0x0ac76a1ab8e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ac76a1ab8f0: 00 00 00 00 00 00 00 00 00 00 00 00 f9 f9 f9 f9 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 Thread T140 created by T5 here: #0 0x7f15a8258685 in __interceptor_pthread_create …/…/…/…/src/libsanitizer/asan/asan_interceptors.cpp:216 #1 0x563750a4608a in WorkQueueManagerRun /home/ubuntu/drone/PX4-Autopilot/platforms/common/px4_work_queue/WorkQueueManager.cpp:324

Thread T5 created by T0 here: #0 0x7f15a8258685 in __interceptor_pthread_create …/…/…/…/src/libsanitizer/asan/asan_interceptors.cpp:216 #1 0x563750a40aab in px4_task_spawn_cmd /home/ubuntu/drone/PX4-Autopilot/platforms/posix/src/px4/common/tasks.cpp:252

==5090==ABORTING

Recommend Patch

After the line of CrsfParser.cpp:319, the additional limitations of the size will prevent the bug as mentioned before.

case PARSER_STATE_CRC: // Fetch the suspected packet as a contingous block of memory if(working_index+CRC_SIZE > CRSF_MAX_PACKET_LEN){ parser_statistics->invalid_unknown_packet_sizes++; parser_state = PARSER_STATE_HEADER; working_segment_size = HEADER_SIZE; working_index=0; buffer_count = QueueBuffer_Count(&rx_queue); continue; } QueueBuffer_PeekBuffer(&rx_queue, 0, process_buffer, working_index + CRC_SIZE);

Impact

If we can create the RC packet remotely and that packet goes into the device where the _rcs_buf reads, the global buffer overflow vulnerability will be triggered so that the drone can behave unexpectedly.

CVE: Latest News

CVE-2023-50976: Transactions API Authorization by oleiman · Pull Request #14969 · redpanda-data/redpanda
CVE-2023-6905
CVE-2023-6903
CVE-2023-6904
CVE-2023-3907