FreeRTOS漏洞整理

近期工作项目中涉及到FreeRTOS系统,和导师一起整理FreeRTOS的相关漏洞,用于后续学习和工作。

漏洞来源:FreeRTOS Security Updates

漏洞编号 漏洞名称 漏洞类型 所属模块
CVE-2024-28115 ARMv7-M MPU和启用MPU支持的ARMv8-M移植版本中存在权限提升 权限升级 FreeRTOS Kernel
/ TCP/IP堆栈网络缓冲区分配存在整数溢出 整数溢出 FreeRTOS-Plus-TCP
CVE-2021-43997 ARMv7-M和ARMv8-M MPU的移植版本存在权限提升 权限提升 FreeRTOS Kernel
CVE-2021-32020 堆中动态内存分配存在整数溢出 整数溢出 FreeRTOS Kernel
CVE-2021-31571 队列创建过程中内存分配存在整数溢出 整数溢出 FreeRTOS Kernel
CVE-2021-31572 流缓冲区创建过程中内存分配存在整数溢出 整数溢出 FreeRTOS Kernel

ARMv7-M MPU和启用MPU支持的ARMv8-M移植版本中存在权限提升

漏洞描述

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.

FreeRTOS内核V10.6.1及V10.6.1版本之前,在存在代码注入和执行的漏洞时,没有对本地提权攻击有足够的防护措施,这些攻击通过使用返回指向编程(Return Oriented Programming)技术实现。漏洞影响ARMv7-M MPU移植版本和开启内存保护单元(MPU)支持(即在配置中将configENABLE_MPU设置为1)的ARMv8-M移植版本。漏洞在V10.6.2版本中通过一个新的MPU封装得到修复。

漏洞详情

修复方案

TCP/IP 堆栈网络缓冲区分配存在整数溢出

漏洞描述

09/10/2021 - FreeRTOS-Plus-TCP V2.3.3 and earlier

  • 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.

在FreeRTOS-Plus-TCP TCP/IP 堆栈网络缓冲区分配方案BufferAllocation_2.c中,计算要分配给网络缓冲区的内存块的大小时, 未对加法操作进行检查,导致出现整数上溢(整数下溢一般出现在减法操作中),分配的网络缓冲区大小远小于请求分配的大小,后续将进一步造成缓冲区溢出。

漏洞详情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
uint8_t * pucGetNetworkBuffer( size_t * pxRequestedSizeBytes )
{
uint8_t * pucEthernetBuffer;
size_t xSize = *pxRequestedSizeBytes;

// 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;
}

return pucEthernetBuffer;
}

在pucGetNetworkBuffer()函数中,首先会检查xSize是否对齐:( xSize & ( sizeof( size_t ) - 1U ) ) != 0U,如果没有对齐,则执行对齐操作:xSize = ( xSize | ( sizeof( size_t ) - 1U ) ) + 1U。问题就出在进行对齐的代码中,如果xSize的值和size_t所能表示的最大值非常接近,进行对齐之后xSize就等于size_t所能表示的最大值,然后再+1,就会造成整数上溢,导致最后xSize的值为0,远小于请求的内存大小,这里需要对xSize的值进行检查。例如:sizeof( size_t )=8,size_t所能表示的最大值为255,如果请求分配的内存大小为250,xSize | ( sizeof( size_t ) - 1U对齐之后大小为255,再+1结果为0。

最后pvPortMalloc( xSize + ipBUFFER_PADDING )函数申请到的内存块小于请求的* pxRequestedSizeBytes,会导致后续使用缓冲区pucEthernetBuffer时产生缓冲区溢出。其中xSize + ipBUFFER_PADDING也可能存在整数溢出。

在pxGetNetworkBufferWithDescriptor()函数和pxResizeNetworkBufferWithDescriptor()函数中同样存在此漏洞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
NetworkBufferDescriptor_t * pxGetNetworkBufferWithDescriptor( size_t xRequestedSizeBytes,
TickType_t xBlockTimeTicks )
{
NetworkBufferDescriptor_t * pxReturn = NULL;
size_t uxCount;

···
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;

if( ( xRequestedSizeBytes & ( sizeof( size_t ) - 1U ) ) != 0U )
{
// 对齐过程存在整数上溢
xRequestedSizeBytes = ( xRequestedSizeBytes | ( sizeof( size_t ) - 1U ) ) + 1U;
}

/* 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 );
···
1
2
3
4
5
6
7
8
9
10
11
12
NetworkBufferDescriptor_t * pxResizeNetworkBufferWithDescriptor( NetworkBufferDescriptor_t * pxNetworkBuffer,
size_t xNewSizeBytes )
{
size_t xOriginalLength;
uint8_t * pucBuffer;

xOriginalLength = pxNetworkBuffer->xDataLength + ipBUFFER_PADDING;
xNewSizeBytes = xNewSizeBytes + ipBUFFER_PADDING;

// 调用pucGetNetworkBuffer()
pucBuffer = pucGetNetworkBuffer( &( xNewSizeBytes ) );
···

修复方案

https://github.com/FreeRTOS/FreeRTOS-Plus-TCP/compare/V2.3.4...main?diff=split&w=#diff-5f070408ed1e32bbb621dc0fd89c3dcc84fa75e279eb5165539d413d6cd12d87R236

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#define baALIGNMENT_BYTES            ( sizeof( size_t ) )
#define baALIGNMENT_MASK ( baALIGNMENT_BYTES - 1U )
#define baADD_WILL_OVERFLOW( a, b ) ( ( a ) > ( SIZE_MAX - ( b ) ) )

uint8_t * pucGetNetworkBuffer( size_t * pxRequestedSizeBytes )
{
uint8_t * pucEthernetBuffer = NULL;
size_t uxMaxAllowedBytes = ( SIZE_MAX >> 1 );
size_t xSize = *pxRequestedSizeBytes;
size_t xBytesRequiredForAlignment, xAllocatedBytes;
BaseType_t xIntegerOverflowed = pdFALSE;

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;
}
}

// baADD_WILL_OVERFLOW检查添加padding之后是否会溢出
if( baADD_WILL_OVERFLOW( xSize, ipBUFFER_PADDING ) == pdFAIL )
{
xAllocatedBytes = xSize + ipBUFFER_PADDING;
}
else
{
xIntegerOverflowed = pdTRUE;
}

// 未产生溢出才分配内存
if( ( xIntegerOverflowed == pdFALSE ) && ( xAllocatedBytes <= uxMaxAllowedBytes ) )
{
*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( 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.

在针对 ARMv7-M 和 ARMv8-M MPU 的移植版本中,非特权任务可以通过调用内部函数xPortRaisePrivilege()提升其特权。

漏洞详情

/portable/Common/mpu_wrappers.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* @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.
*/
void vPortResetPrivilege( BaseType_t xRunningPrivileged );
/*-----------------------------------------------------------*/

BaseType_t xPortRaisePrivilege( void ) /* FREERTOS_SYSTEM_CALL */
{
BaseType_t xRunningPrivileged;

/* Check whether the processor is already privileged. */
xRunningPrivileged = portIS_PRIVILEGED();

/* If the processor is not already privileged, raise privilege. */
if( xRunningPrivileged == pdFALSE )
{
portRAISE_PRIVILEGE();
}

return xRunningPrivileged;
}
/*-----------------------------------------------------------*/

void vPortResetPrivilege( BaseType_t xRunningPrivileged )
{
if( xRunningPrivileged == pdFALSE )
{
portRESET_PRIVILEGE();
}
}

在mpu_wrappers.c中,xPortRaisePrivilege()和vPortResetPrivilege()都被定义为函数,可以链接到xPortRaisePrivilege()和vPortResetPrivilege()函数标识符的代码都可以调用它们,这意味着非内核代码(如果不恰当地实现)有可能调用xPortRaisePrivilege()和vPortResetPrivilege()函数提升权限。

修复方案

https://github.com/FreeRTOS/FreeRTOS-Kernel/commit/7a3848753b303cffe658abc11110d008669f7021

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
diff --git a/include/mpu_wrappers.h b/include/mpu_wrappers.h
index 7f07d0001e..c44184d729 100644
--- a/include/mpu_wrappers.h
+++ b/include/mpu_wrappers.h
@@ -168,11 +168,41 @@

#else /* MPU_WRAPPERS_INCLUDED_FROM_API_FILE */

-/* Ensure API functions go in the privileged execution section. */
+ /* Ensure API functions go in the privileged execution section. */
#define PRIVILEGED_FUNCTION __attribute__( ( section( "privileged_functions" ) ) )
#define PRIVILEGED_DATA __attribute__( ( section( "privileged_data" ) ) )
#define FREERTOS_SYSTEM_CALL __attribute__( ( section( "freertos_system_calls" ) ) )

+ /**
+ * @brief Calls the port specific code to raise the privilege.
+ *
+ * Sets xRunningPrivileged to pdFALSE if privilege was raised, else sets
+ * it to pdTRUE.
+ */
+ #define xPortRaisePrivilege( xRunningPrivileged ) \
+ { \
+ /* Check whether the processor is already privileged. */ \
+ xRunningPrivileged = portIS_PRIVILEGED(); \
+ \
+ /* If the processor is not already privileged, raise privilege. */ \
+ if( xRunningPrivileged == pdFALSE ) \
+ { \
+ portRAISE_PRIVILEGE(); \
+ } \
+ }
+
+ /**
+ * @brief If xRunningPrivileged is not pdTRUE, calls the port specific
+ * code to reset the privilege, otherwise does nothing.
+ */
+ #define vPortResetPrivilege( xRunningPrivileged ) \
+ { \
+ if( xRunningPrivileged == pdFALSE ) \
+ { \
+ portRESET_PRIVILEGE(); \
+ } \
+ }
+
#endif /* MPU_WRAPPERS_INCLUDED_FROM_API_FILE */

#else /* portUSING_MPU_WRAPPERS */
diff --git a/portable/Common/mpu_wrappers.c b/portable/Common/mpu_wrappers.c
index 7a04fb8a7d..c5b71d14b8 100644
--- a/portable/Common/mpu_wrappers.c
+++ b/portable/Common/mpu_wrappers.c
@@ -46,45 +46,6 @@
#include "mpu_prototypes.h"

#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE
-
-/**
- * @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.
- */
-void vPortResetPrivilege( BaseType_t xRunningPrivileged );
-/*-----------------------------------------------------------*/
-
-BaseType_t xPortRaisePrivilege( void ) /* FREERTOS_SYSTEM_CALL */
-{
- BaseType_t xRunningPrivileged;
-
- /* Check whether the processor is already privileged. */
- xRunningPrivileged = portIS_PRIVILEGED();
-
- /* If the processor is not already privileged, raise privilege. */
- if( xRunningPrivileged == pdFALSE )
- {
- portRAISE_PRIVILEGE();
- }
-
- return xRunningPrivileged;
-}
-/*-----------------------------------------------------------*/
-
-void vPortResetPrivilege( BaseType_t xRunningPrivileged )
-{
- if( xRunningPrivileged == pdFALSE )
- {
- portRESET_PRIVILEGE();
- }
-}

修复方法是将xPortRaisePrivilege()和vPortResetPrivilege()函数重新定义为宏,放在mpu_wrappers.h中,将函数转换为宏可以在一定程度上减小风险:

  • 宏在预处理阶段直接展开到调用宏的代码中,宏没有一个在链接阶段可解析和可调用的函数地址,因此难以被非预期的代码直接调用;
  • 宏可以在编译时控制可见性,比如通过在宏的定义前使用 static 或其他编译器特定的指示,确保这些宏只能在预定义的文件或模块中使用;
  • 宏还可以增加额外的安全检查或条件编译逻辑,以便仅在确定是内核代码时才允许权限升级。

堆中动态内存分配存在整数溢出

漏洞描述

12/15/2020 - FreeRTOS Kernel V10.4.2 and earlier

  • 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.

在heap2.c中,计算要分配的内存块的大小时, 未对加法操作进行检查,导致出现整数上溢,分配的内存块大小远小于请求分配的大小,后续将进一步造成缓冲区溢出。

漏洞详情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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 )
{
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
}
#endif
return pvReturn;
}

在进行xWantedSize大小对齐时,没有对对齐后的xWantedSize大小进行检查,导致可能出现整数上溢,并将未检查的xWantedSize值传递给后面的代码,直接进行内存分配,导致分配的内存大小远小于请求的大小,后续将进一步造成缓冲区溢出。

修复方案

https://github.com/FreeRTOS/FreeRTOS-Kernel/commit/c7a9a01c94987082b223d3e59969ede64363da63

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
diff --git a/portable/MemMang/heap_2.c b/portable/MemMang/heap_2.c
index 640cd54fdd..e132ae3ea8 100644
--- a/portable/MemMang/heap_2.c
+++ b/portable/MemMang/heap_2.c
@@ -132,21 +131,32 @@ void * pvPortMalloc( size_t xWantedSize )
xHeapHasBeenInitialised = pdTRUE;
}

- /* The wanted size is increased so it can contain a BlockLink_t
+ /* The wanted size must be increased so it can contain a BlockLink_t
* structure in addition to the requested amount of bytes. */
- if( xWantedSize > 0 )
+ if( ( xWantedSize > 0 ) &&
+ ( ( xWantedSize + heapSTRUCT_SIZE ) > xWantedSize ) ) /* Overflow check */
{
xWantedSize += heapSTRUCT_SIZE;

- /* Ensure that blocks are always aligned to the required number of bytes. */
- if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 )
+ /* Byte alignment required. Check for overflow. */
+ if( ( xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ) )
+ > xWantedSize )
{
- /* Byte alignment required. */
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
+ configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 );
}
+ else
+ {
+ xWantedSize = 0;
+ }
+ }
+ else
+ {
+ xWantedSize = 0;
}

- if( ( xWantedSize > 0 ) && ( xWantedSize < configADJUSTED_HEAP_SIZE ) )
+
+ if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
{
/* Blocks are stored in byte order - traverse the list from the start
* (smallest) block until one of adequate size is found. */

修复方案是检查xWantedSize对齐之后的值,如果其比对齐之前的xWantedSize小,那说明出现溢出;如果没有溢出,那么对齐之后的xWantedSize值应该大于对齐之前的值。

队列创建过程中内存分配存在整数溢出

漏洞描述

12/15/2020 - FreeRTOS Kernel V10.4.2 and earlier

  • 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

在queue.c中,队列内存分配期间可能存在未经检查的加法溢出(整数溢出)。

漏洞详情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
const uint8_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;
}

在调用pvPortMalloc()函数分配内存之前,没有检查参数sizeof( Queue_t ) + xQueueSizeInBytes是否会溢出,如果溢出,将导致分配的内存远小于所需的内存大小。xQueueSizeInBytes表示队列所需存储空间的大小(以字节为单位),它决定了队列中可以存储多少数据;sizeof( Queue_t ) + xQueueSizeInBytes表示队列对象Queue_t的总大小是Queue_t结构体的大小加上队列数据存储区的大小。

修复方案

https://github.com/FreeRTOS/FreeRTOS-Kernel/commit/47338393f1f79558f6144213409f09f81d7c4837

1
2
3
4
5
6
7
8
9
10
11
12
13
14
diff --git a/queue.c b/queue.c
index d2e27e55a5..b01dfd11ff 100644
--- a/queue.c
+++ b/queue.c
@@ -397,6 +397,9 @@ BaseType_t xQueueGenericReset( QueueHandle_t xQueue,
/* Check for multiplication overflow. */
configASSERT( ( uxItemSize == 0 ) || ( uxQueueLength == ( xQueueSizeInBytes / uxItemSize ) ) );

+ /* Check for addition overflow. */
+ configASSERT( ( sizeof( Queue_t ) + xQueueSizeInBytes ) > xQueueSizeInBytes );
+
/* 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

修复方法是添加一个断言,如果sizeof( Queue_t ) + xQueueSizeInBytes的大小大于xQueueSizeInBytes,则没有产生溢出,否则,出现溢出,断言configASSERT就会报错,触发错误处理。

流缓冲区创建过程中内存分配存在整数溢出

漏洞描述

12/15/2020 - FreeRTOS Kernel V10.4.2 and earlier

  • 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

在stream_buffer.c中,创建流缓冲区期间可能存在未经检查的加法溢出(整数溢出)。

漏洞详情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#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. */
}

在调用pvPortMalloc()函数分配内存之前,没有检查参数xBufferSizeBytes + sizeof( StreamBuffer_t )是否会溢出,如果溢出,将导致分配的内存远小于所需的内存大小。

修复方案

https://github.com/FreeRTOS/FreeRTOS-Kernel/commit/d05b9c123f2bf9090bce386a244fc934ae44db5b

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
diff --git a/stream_buffer.c b/stream_buffer.c
index 03cfc06156..fec03a7816 100644
--- a/stream_buffer.c
+++ b/stream_buffer.c
@@ -258,8 +258,16 @@ static void prvInitialiseNewStreamBuffer( StreamBuffer_t * const pxStreamBuffer,
* 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++;
- pucAllocatedMemory = ( uint8_t * ) pvPortMalloc( xBufferSizeBytes + sizeof( StreamBuffer_t ) ); /*lint !e9079 malloc() only returns void*. */
+ if( xBufferSizeBytes < ( xBufferSizeBytes + 1 + sizeof( StreamBuffer_t ) ) )
+ {
+ xBufferSizeBytes++;
+ pucAllocatedMemory = ( uint8_t * ) pvPortMalloc( xBufferSizeBytes + sizeof( StreamBuffer_t ) ); /*lint !e9079 malloc() only returns void*. */
+ }
+ else
+ {
+ pucAllocatedMemory = NULL;
+ }
+

if( pucAllocatedMemory != NULL )
{

修复方法是检查xBufferSizeBytes的值是否比xBufferSizeBytes + 1 + sizeof( StreamBuffer_t )的值要小,如果比相加之后的值小,说明没有溢出,否则产生溢出。

参考资料:

TCP/IP 堆栈网络缓冲区分配方案

FreeRTOS Security Updates