FreeRTOS Kernel versions through 10.6.1 do not sufficiently protect against local privilege escalation via Return Oriented Programming techniques should a vulnerability exist that allows code injection and execution. These issues affect ARMv7-M MPU ports, and ARMv8-M ports with Memory Protected Unit (MPU) support enabled (i.e. configENABLE_MPU set to 1). These issues are fixed in V10.6.2 with a new MPU wrapper. A threat model detailing these issues and corresponding mitigations is also published.
In BufferAllocation_2.c, there is an unchecked possible addition overflow when calculating the size of the block of memory to be allocated for a network buffer that could result in the size overflowing and the allocation returning success but allocating only a fraction of the memory asked for. With default settings, this would only occur when attempting to allocate within 12 bytes of 4 GB.
// xSize要大于最小值 if( xSize < baMINIMAL_BUFFER_SIZE ) { /* Buffers must be at least large enough to hold a TCP-packet with * headers, or an ARP packet, in case TCP is not included. */ xSize = baMINIMAL_BUFFER_SIZE; }
/* Round up xSize to the nearest multiple of N bytes, * where N equals 'sizeof( size_t )'. */ // 检查是否对齐 if( ( xSize & ( sizeof( size_t ) - 1U ) ) != 0U ) { // 对齐过程存在整数上溢 xSize = ( xSize | ( sizeof( size_t ) - 1U ) ) + 1U; }
*pxRequestedSizeBytes = xSize;
/* Allocate a buffer large enough to store the requested Ethernet frame size * and a pointer to a network buffer structure (hence the addition of * ipBUFFER_PADDING bytes). */ pucEthernetBuffer = ( uint8_t * ) pvPortMalloc( xSize + ipBUFFER_PADDING ); configASSERT( pucEthernetBuffer != NULL );
if( pucEthernetBuffer != NULL ) { /* Enough space is left at the start of the buffer to place a pointer to * the network buffer structure that references this Ethernet buffer. * Return a pointer to the start of the Ethernet buffer itself. */ pucEthernetBuffer += ipBUFFER_PADDING; }
··· if( xRequestedSizeBytes > 0U ) { if( ( xRequestedSizeBytes < ( size_t ) baMINIMAL_BUFFER_SIZE ) ) { /* ARP packets can replace application packets, so the storage must be * at least large enough to hold an ARP. */ xRequestedSizeBytes = baMINIMAL_BUFFER_SIZE; }
/* Add 2 bytes to xRequestedSizeBytes and round up xRequestedSizeBytes * to the nearest multiple of N bytes, where N equals 'sizeof( size_t )'. */ xRequestedSizeBytes += 2U;
/* Extra space is obtained so a pointer to the network buffer can * be stored at the beginning of the buffer. */ pxReturn->pucEthernetBuffer = ( uint8_t * ) pvPortMalloc( xRequestedSizeBytes + ipBUFFER_PADDING ); ···
if( xSize < baMINIMAL_BUFFER_SIZE ) { /* Buffers must be at least large enough to hold a TCP-packet with * headers, or an ARP packet, in case TCP is not included. */ xSize = baMINIMAL_BUFFER_SIZE; }
/* Round up xSize to the nearest multiple of N bytes, * where N equals 'sizeof( size_t )'. */ if( ( xSize & baALIGNMENT_MASK ) != 0U ) { // xBytesRequiredForAlignment表示对齐需要补齐的字节数 xBytesRequiredForAlignment = baALIGNMENT_BYTES - ( xSize & baALIGNMENT_MASK ); // baADD_WILL_OVERFLOW检查对齐之后是否会溢出 if( baADD_WILL_OVERFLOW( xSize, xBytesRequiredForAlignment ) == pdFAIL ) { xSize += xBytesRequiredForAlignment; } else { xIntegerOverflowed = pdTRUE; } }
/* Allocate a buffer large enough to store the requested Ethernet frame size * and a pointer to a network buffer structure (hence the addition of * ipBUFFER_PADDING bytes). */ pucEthernetBuffer = ( uint8_t * ) pvPortMalloc( xAllocatedBytes ); configASSERT( pucEthernetBuffer != NULL );
if( pucEthernetBuffer != NULL ) { /* Enough space is left at the start of the buffer to place a pointer to * the network buffer structure that references this Ethernet buffer. * Return a pointer to the start of the Ethernet buffer itself. */
/* MISRA Ref 18.4.1 [Usage of +, -, += and -= operators on expression of pointer type]. */ /* More details at: https://github.com/FreeRTOS/FreeRTOS-Plus-TCP/blob/main/MISRA.md#rule-184. */ /* coverity[misra_c_2012_rule_18_4_violation] */ pucEthernetBuffer += ipBUFFER_PADDING; } }
return pucEthernetBuffer; }
ARMv7-M 和 ARMv8-M MPU 的移植版本存在权限提升
漏洞描述
11/12/2021 - FreeRTOS Kernel versions 10.2.0 to 10.4.5 (inclusive)
ARMv7-M and ARMv8-M MPU ports: It is possible for an unprivileged task to raise its privilege by calling the internal function xPortRaisePrivilege.
The public CVE record for this can be found at MITRE: CVE-2021-43997.
/** * @brief Calls the port specific code to raise the privilege. * * @return pdFALSE if privilege was raised, pdTRUE otherwise. */ BaseType_t xPortRaisePrivilege( void ) FREERTOS_SYSTEM_CALL;
/** * @brief If xRunningPrivileged is not pdTRUE, calls the port specific * code to reset the privilege, otherwise does nothing. */ voidvPortResetPrivilege( BaseType_t xRunningPrivileged ); /*-----------------------------------------------------------*/
In heap2.c there is an unchecked possible addition overflow when calculating the size of the block of memory to be allocated that could result in the size overflowing and the allocation returning success but allocating only a fraction of the memory asked for. This will only affect code where the amount of memory being allocated is within 8 bytes of 4 GB.
FreeRTOS V10.4.3 and newer contains additional code that checks for and prevents these potential overflows.
The public CVE record for this can be found at MITRE: CVE-2021-32020.
void * pvPortMalloc( size_t xWantedSize ) { BlockLink_t * pxBlock, * pxPreviousBlock, * pxNewBlockLink; static BaseType_t xHeapHasBeenInitialised = pdFALSE; void * pvReturn = NULL; vTaskSuspendAll(); { /* If this is the first call to malloc then the heap will require * initialisation to setup the list of free blocks. */ if( xHeapHasBeenInitialised == pdFALSE ) { prvHeapInit(); xHeapHasBeenInitialised = pdTRUE; }
/* The wanted size is increased so it can contain a BlockLink_t * structure in addition to the requested amount of bytes. */ if( xWantedSize > 0 ) { xWantedSize += heapSTRUCT_SIZE;
/* Ensure that blocks are always aligned to the required number of bytes. */ if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 ) { /* Byte alignment required. */ // 对齐过程存在溢出 xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ); } }
if( ( xWantedSize > 0 ) && ( xWantedSize < configADJUSTED_HEAP_SIZE ) ) { /* Blocks are stored in byte order - traverse the list from the start * (smallest) block until one of adequate size is found. */ pxPreviousBlock = &xStart; pxBlock = xStart.pxNextFreeBlock; // 寻找一个大小合适的块,大小比xWantedSize大 while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) ) { pxPreviousBlock = pxBlock; pxBlock = pxBlock->pxNextFreeBlock; } /* If we found the end marker then a block of adequate size was not found. */ if( pxBlock != &xEnd ) { /* Return the memory space - jumping over the BlockLink_t structure * at its start. */ // 分配的内存块 pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE ); /* This block is being returned for use so must be taken out of the * list of free blocks. */ pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock; /* If the block is larger than required it can be split into two. */ if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE ) { /* This block is to be split into two. Create a new block * following the number of bytes requested. The void cast is * used to prevent byte alignment warnings from the compiler. */ pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize ); /* Calculate the sizes of two blocks split from the single * block. */ pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize; pxBlock->xBlockSize = xWantedSize; /* Insert the new block into the list of free blocks. */ prvInsertBlockIntoFreeList( ( pxNewBlockLink ) ); } xFreeBytesRemaining -= pxBlock->xBlockSize; } } traceMALLOC( pvReturn, xWantedSize ); } ( void ) xTaskResumeAll(); #if ( configUSE_MALLOC_FAILED_HOOK == 1 ) { if( pvReturn == NULL ) { externvoidvApplicationMallocFailedHook( void ); vApplicationMallocFailedHook(); } } #endif return pvReturn; }
In queue.c there is an unchecked possible addition overflow during queue allocation. This will only affect code where the size of the queue is within sizeof(queue_t) bytes of 4GB.
FreeRTOS V10.4.3 and newer contains additional code that checks for and prevents these potential overflows.
The public CVE record for this can be found at MITRE: CVE-2021-31571
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, constuint8_t ucQueueType ) { Queue_t * pxNewQueue; size_t xQueueSizeInBytes; uint8_t * pucQueueStorage; configASSERT( uxQueueLength > ( UBaseType_t ) 0 ); /* Allocate enough space to hold the maximum number of items that * can be in the queue at any time. It is valid for uxItemSize to be * zero in the case the queue is used as a semaphore. */ xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ /* Check for multiplication overflow. */ configASSERT( ( uxItemSize == 0 ) || ( uxQueueLength == ( xQueueSizeInBytes / uxItemSize ) ) );
/* Allocate the queue and storage area. Justification for MISRA * deviation as follows: pvPortMalloc() always ensures returned memory * blocks are aligned per the requirements of the MCU stack. In this case * pvPortMalloc() must return a pointer that is guaranteed to meet the * alignment requirements of the Queue_t structure - which in this case * is an int8_t *. Therefore, whenever the stack alignment requirements * are greater than or equal to the pointer to char requirements the cast * is safe. In other cases alignment requirements are not strict (one or * two bytes). */ // 在调用pvPortMalloc()分配内存之前,没有检查sizeof( Queue_t ) + xQueueSizeInBytes是否存在溢出 pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); /*lint !e9087 !e9079 see comment above. */ if( pxNewQueue != NULL ) { /* Jump past the queue structure to find the location of the queue * storage area. */ pucQueueStorage = ( uint8_t * ) pxNewQueue; pucQueueStorage += sizeof( Queue_t ); /*lint !e9016 Pointer arithmetic allowed on char types, especially when it assists conveying intent. */ #if ( configSUPPORT_STATIC_ALLOCATION == 1 ) { /* Queues can be created either statically or dynamically, so * note this task was created dynamically in case it is later * deleted. */ pxNewQueue->ucStaticallyAllocated = pdFALSE; } #endif/* configSUPPORT_STATIC_ALLOCATION */ prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue ); } else { traceQUEUE_CREATE_FAILED( ucQueueType ); mtCOVERAGE_TEST_MARKER(); } return pxNewQueue; }
In stream_buffer.c there is an unchecked possible addition overflow during steam buffer creation. This will only affect code where the size of the stream buffer is within sizeof(StreamBuffer_t) bytes of 4GB.
FreeRTOS V10.4.3 and newer contains additional code that checks for and prevents these potential overflows.
The public CVE record for this can be found at MITRE: CVE-2021-31572
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) StreamBufferHandle_t xStreamBufferGenericCreate( size_t xBufferSizeBytes, size_t xTriggerLevelBytes, BaseType_t xIsMessageBuffer ) { uint8_t * pucAllocatedMemory; uint8_t ucFlags; /* In case the stream buffer is going to be used as a message buffer * (that is, it will hold discrete messages with a little meta data that * says how big the next message is) check the buffer will be large enough * to hold at least one message. */ if( xIsMessageBuffer == pdTRUE ) { /* Is a message buffer but not statically allocated. */ ucFlags = sbFLAGS_IS_MESSAGE_BUFFER; configASSERT( xBufferSizeBytes > sbBYTES_TO_STORE_MESSAGE_LENGTH ); } else { /* Not a message buffer and not statically allocated. */ ucFlags = 0; configASSERT( xBufferSizeBytes > 0 ); } configASSERT( xTriggerLevelBytes <= xBufferSizeBytes ); /* A trigger level of 0 would cause a waiting task to unblock even when * the buffer was empty. */ if( xTriggerLevelBytes == ( size_t ) 0 ) { xTriggerLevelBytes = ( size_t ) 1; } /* A stream buffer requires a StreamBuffer_t structure and a buffer. * Both are allocated in a single call to pvPortMalloc(). The * StreamBuffer_t structure is placed at the start of the allocated memory * and the buffer follows immediately after. The requested size is * incremented so the free space is returned as the user would expect - * this is a quirk of the implementation that means otherwise the free * space would be reported as one byte smaller than would be logically * expected. */ xBufferSizeBytes++; // 在调用pvPortMalloc()分配内存之前,没有检查xBufferSizeBytes + sizeof( StreamBuffer_t )是否存在溢出 pucAllocatedMemory = ( uint8_t * ) pvPortMalloc( xBufferSizeBytes + sizeof( StreamBuffer_t ) ); /*lint !e9079 malloc() only returns void*. */ if( pucAllocatedMemory != NULL ) { prvInitialiseNewStreamBuffer( ( StreamBuffer_t * ) pucAllocatedMemory, /* Structure at the start of the allocated memory. *//*lint !e9087 Safe cast as allocated memory is aligned. *//*lint !e826 Area is not too small and alignment is guaranteed provided malloc() behaves as expected and returns aligned buffer. */ pucAllocatedMemory + sizeof( StreamBuffer_t ), /* Storage area follows. *//*lint !e9016 Indexing past structure valid for uint8_t pointer, also storage area has no alignment requirement. */ xBufferSizeBytes, xTriggerLevelBytes, ucFlags ); traceSTREAM_BUFFER_CREATE( ( ( StreamBuffer_t * ) pucAllocatedMemory ), xIsMessageBuffer ); } else { traceSTREAM_BUFFER_CREATE_FAILED( xIsMessageBuffer ); } return ( StreamBufferHandle_t ) pucAllocatedMemory; /*lint !e9087 !e826 Safe cast as allocated memory is aligned. */ }