Exploiting an RCE bug in the UDP Protocol implemented in FreeRTOS

Recently, I saw a report about several bugs that were found on FreeRTOS. Curiosity got the best of me, and I started to take a look to see what can be done from the IPS side to protect our customers because of importance of IoT devices and the popularity of this operating system. (Since the initial report more details have been made available here, CVE-2018-16525.)

In this post I will just elaborate on a single RCE bug that I have managed to exploit in the UDP protocol which is implemented in FreeRTOS+TCP.

RTOS, Real Time Operating System, is a type of operating system that provides deterministic execution. AWS FreeRTOS is a class of RTOS from Amazon Web Services that supports more than 35 architectures. Its popularity once led to it being downloaded once every three minutes during 2017. It has a FreeRTOS core plus a TCP/IP stack and many more modules.

To get started, I used a demo which is provided on the freertos site.

With sufficient information, it was time to dig in. I used Amazon FreeRTOS v1.3.1 and FreeRTOS v.10.0.0 as references.

FreeRTOS_IP.c is responsible for handling packets and processing them based on protocols. If the packet contains a UDP frame, it will be processed by the following code:

case ipPROTOCOL_UDP :
{
    /* The IP packet contained a UDP frame. */
    UDPPacket_t *pxUDPPacket = ( UDPPacket_t * ) ( pxNetworkBuffer->pucEthernetBuffer );

    /* Note the header values required prior to the
    checksum generation as the checksum pseudo header
    may clobber some of these values. */

    pxNetworkBuffer->xDataLength = FreeRTOS_ntohs( pxUDPPacket->xUDPHeader.usLength ) – sizeof( UDPHeader_t );
    /* HT:endian: fields in pxNetworkBuffer (usPort, ulIPAddress) are network order */
    pxNetworkBuffer->usPort = pxUDPPacket->xUDPHeader.usSourcePort;
                              pxNetworkBuffer->ulIPAddress = pxUDPPacket->xIPHeader.ulSourceIPAddress;

    if( xProcessReceivedUDPPacket( pxNetworkBuffer, pxUDPPacket->xUDPHeader.usDestinationPort ) == pdPASS )
   {
      eReturn = eFrameConsumed;
   }
}
break;    

 

As we can see, there are no checks on incoming packets. Now let’s take a look at the function “xProcessReceivedUDPPacket”. This function is implemented on the “FreeRTOS_UDP_IP.c” file.

 

BaseType_t xProcessReceivedUDPPacket( NetworkBufferDescriptor_t *pxNetworkBuffer, uint16_t usPort )
{
BaseType_t xReturn = pdPASS;
FreeRTOS_Socket_t *pxSocket;
UDPPacket_t *pxUDPPacket = (UDPPacket_t *) pxNetworkBuffer->pucEthernetBuffer;
      pxSocket = pxUDPSocketLookup( usPort );
      if( pxSocket )
      {
            vARPRefreshCacheEntry( &( pxUDPPacket->xEthernetHeader.xSourceAddress ), pxUDPPacket->xIPHeader.ulSourceIPAddress );
            if( xReturn == pdPASS )
            {
                  vTaskSuspendAll();
                  {
                        if( xReturn == pdPASS )
                        {
                              taskENTER_CRITICAL();
                              {
                                    vListInsertEnd( &( pxSocket->u.xUDP.xWaitingPacketsList ), &( pxNetworkBuffer->xBufferListItem ) );
                              }
                              taskEXIT_CRITICAL();
                        }
                  }
                  xTaskResumeAll();

                  /* Set the socket’s receive event */
                  }
            /* unrelated parts are cropped */    

 

After this, the received packets will be consumed by the appropriate socket, which in this case is “FreeRTOS_recvfrom” in “FreeRTOS_socket.c”.

 

int32_t FreeRTOS_recvfrom( xSocket_t xSocket, void *pvBuffer, size_t xBufferLength, uint32_t ulFlags, struct freertos_sockaddr *pxSourceAddress, socklen_t *pxSourceAddressLength )
{
xNetworkBufferDescriptor_t *pxNetworkBuffer;
int32_t lReturn;
xFreeRTOS_Socket_t *pxSocket;

      pxSocket = ( xFreeRTOS_Socket_t * ) xSocket;

      /* The function prototype is designed to maintain the expected Berkeley
      sockets standard, but this implementation does not use all the parameters. */

      ( void ) pxSourceAddressLength;

      if( socketSOCKET_IS_BOUND( pxSocket ) != pdFALSE )
      {
            /* The semaphore is given when received data is queued on the socket. */
            if( xSemaphoreTake( pxSocket->xWaitingPacketSemaphore, pxSocket->xReceiveBlockTime ) == pdPASS )
            {
                  taskENTER_CRITICAL();
                  {
                        configASSERT( ( listCURRENT_LIST_LENGTH( &( pxSocket->xWaitingPacketsList ) ) > 0U ) );

                        /* The owner of the list item is the network buffer. */
                        pxNetworkBuffer = ( xNetworkBufferDescriptor_t * ) listGET_OWNER_OF_HEAD_ENTRY( &( pxSocket->xWaitingPacketsList ) );

                        /* Remove the network buffer from the list of buffers waiting to
                        be processed by the socket. */

                        uxListRemove( &( pxNetworkBuffer->xBufferListItem ) );
                  }
                  taskEXIT_CRITICAL();

                  if( ( ulFlags & FREERTOS_ZERO_COPY ) == 0 )
                  {
                        /* The zero copy flag is not set.  Truncate the length if it
                        won’t fit in the provided buffer. */

                        if( pxNetworkBuffer->xDataLength > xBufferLength )
                        {
                              iptraceRECVFROM_DISCARDING_BYTES( ( xBufferLength – pxNetworkBuffer->xDataLength ) );
                              pxNetworkBuffer->xDataLength = xBufferLength;
                        }

                        /* Copy the received data into the provided buffer, then
                        release the network buffer. */

                        memcpy( pvBuffer, ( void * ) &( pxNetworkBuffer->pucEthernetBuffer[ ipUDP_PAYLOAD_OFFSET ] ), pxNetworkBuffer->xDataLength );
                        vNetworkBufferRelease( pxNetworkBuffer );
                  }
                  else
                  {

                  /* unrelated codes are cropped */
                  }    

 

The memcpy in the above code seems interesting since there are no checks on the incoming packet. All we need to do is to find a code that satisfies our need for “ulFlags”. Here is what I found in the API references:

 

 /* FreeRTOS+UDP sockets include */
#define “FreeRTOS_sockets.h”

void vStandardReceiveExample( xSocket_t xSocket )
{
/* Note – the task stack must be big enough to hold this array!. */
uint8_t ucBuffer[ 128 ];
int8_t cIPAddressString[ 16 ];
struct freertos_sockaddr xSourceAddress;
int32_t iReturned;

    /* Receive into the buffer with ulFlags set to 0, so the FREERTOS_ZERO_COPY bit
    is clear. */

    iReturned = FreeRTOS_recvfrom(
                                    /* The socket data is being received on. */
                                    xSocket,
                                    /* The buffer into which received data will be
                                    copied. */

                                    ucBuffer,
                                    /* The length of the buffer into which data will be
                                    copied. */

                                    128,
                                    /* ulFlags with the FREERTOS_ZERO_COPY bit clear. */
                                    0,
                                    /* Will get set to the source of the received data. */
                                    &xSourceAddress,
                                    /* Not used but should be set as shown. */
                                    sizeof( xSourceAddress )
                               );

    if( iReturned > 0 )
    {
        /* Data was received from the socket.  Prepare the IP address for
        printing to the console by converting it to a string. */

        FreeRTOS_inet_ntoa( xSourceAddress.sin_addr, ( char * ) cIPAddressString );

        /* Print out details of the data source. */
        printf( “Received %d bytes from IP address %s port number %drn”,
                    iReturned, /* The number of bytes received. */
                    cIPAddressString, /* The IP address that sent the data. */
                    FreeRTOS_ntohs( xSourceAddress.sin_port ) ); /* The source port. */
    }
}    

 

Is this a UDP header with a very big value as its length field? No, it gets truncated, and the part that cannot fit is dropped. Hmmm, how about something less than “sizeof( UDPHeader_t )”? Then we have “pxNetworkBuffer->xDataLength” with a negative value. So it never gets updated in the processing, and in memcpy it is going to be a big number.

Lets try:

As can be seen in the snapshot, I am actually able to hijack the execution flow and execute my code. To mitigate this bug, the following patch has been applied to the mainstream code:

 

    if ( pxNetworkBuffer->xDataLength >= sizeof( UDPPacket_t ) )
                    {
                        /* Ensure that downstream UDP packet handling has the lesser
                         * of: the actual network buffer Ethernet frame length, or
                         * the sender’s UDP packet header payload length, minus the
                         * size of the UDP header.
                         *
                         * The size of the UDP packet structure in this implementation
                         * includes the size of the Ethernet header, the size of
                         * the IP header, and the size of the UDP header.
                         */

                        
                        pxNetworkBuffer->xDataLength -= sizeof( UDPPacket_t );
                        if( ( FreeRTOS_ntohs( pxUDPPacket->xUDPHeader.usLength ) – sizeof( UDPHeader_t ) ) <
                                pxNetworkBuffer->xDataLength )
                        {
                            pxNetworkBuffer->xDataLength = FreeRTOS_ntohs( pxUDPPacket->xUDPHeader.usLength ) –
                                    sizeof( UDPHeader_t );
                        }
                         /* Fields in pxNetworkBuffer (usPort, ulIPAddress) are network order. */
                        pxNetworkBuffer->usPort = pxUDPPacket->xUDPHeader.usSourcePort;
                        pxNetworkBuffer->ulIPAddress = pxUDPPacket->xIPHeader.ulSourceIPAddress;
                         /* ipconfigDRIVER_INCLUDED_RX_IP_CHECKSUM:
                         * In some cases, the upper-layer checksum has been calculated
                         * by the NIC driver.
                         *
                         * Pass the packet payload to the UDP sockets implementation. */

                        if( xProcessReceivedUDPPacket( pxNetworkBuffer,
                                                       pxUDPPacket->xUDPHeader.usDestinationPort ) == pdPASS )
                        {
                            eReturn = eFrameConsumed;
                        }
                    }

Fortinet customers are protected by IPS signature “AWS.FreeRTOS.UDP.Buffer.Overflow”

 

References

[1] https://www.freertos.org/FreeRTOS-Windows-Simulator-Emulator-for-Visual-Studio-and-Eclipse-MingW.html

[2] https://www.freertos.org/a00104.html

[3] https://www.freertos.org/FreeRTOS-Plus/FreeRTOS_Plus_TCP/examples_FreeRTOS_simulator.html

[4] https://freertos.org/FreeRTOS-Plus/FreeRTOS_Plus_UDP/API/recvfrom.shtml

[5] https://blog.zimperium.com/freertos-tcpip-stack-vulnerabilities-put-wide-range-devices-risk-compromise-smart-homes-critical-infrastructure-systems/

[6] https://github.com/aws/amazon-freertos

Sign up for our weekly FortiGuard Threat Brief.

Know your vulnerabilities – get the facts about your network security. A Fortinet Cyber Threat Assessment can help you better understand: Security and Threat Prevention, User Productivity, and Network Utilization and Performance.



*** This is a Security Bloggers Network syndicated blog from Fortinet All Blogs authored by Fortinet All Blogs. Read the original post at: http://feedproxy.google.com/~r/fortinet/blogs/~3/wYjuB-dln9Q/exploiting-an-rce-bug-in-the-udp-protocol-implemented-in-freerto.html