Enhancing System Robustness with FreeRTOS MPU

Enhancing System Robustness with FreeRTOS MPU

The MPU (Memory Protection Unit) is an optional module in the Cortex-M core, allowing memory mapping (including Flash, RAM, and peripherals) to be divided into several regions, each assigned different access permissions.

FreeRTOS-MPU is a secure version of FreeRTOS designed for MPU, supporting microcontrollers with ARMv7-M (Cortex-M3, Cortex-M4, and Cortex-M7) and ARMv8-M (Cortex-M23 and Cortex-M33) cores.

There are two versions of FreeRTOS ported for ARMv7-M, one supporting MPU and one not. For ARMv8-M, there is only one ported version, with compile switches controlling MPU support.

FreeRTOS enhances application robustness and security by dividing tasks into privileged and unprivileged modes and restricting access to RAM, peripherals, executable code, and task stack memory. For example, preventing code execution from RAM can yield significant benefits by preventing numerous attack vectors such as buffer overflow vulnerabilities or the execution of malicious code loaded into RAM.

Using MPU inevitably complicates application design; first, it is essential to determine the memory area limits for the MPU and describe them to the RTOS, and second, the MPU restricts what application tasks can and cannot do.

MPU Strategies

Creating an application that restricts each task to its own memory area may be the safest option, but it is also the most complex to design and implement. It is often better to use an MPU to create a pseudo-process and thread model—allowing thread groups to share memory space. For example, creating a memory space accessible by trusted first-party code and a memory space accessible only by untrusted third-party code.

Features of FreeRTOS-MPU

Compatible with standard ports of ARM Cortex-M3 and Cortex-M4F.

Tasks can be created to run in privileged or unprivileged mode. Unprivileged tasks can only access their own stack and up to three user-defined memory regions (three per task). User-defined memory regions are allocated to tasks at creation and can be reconfigured at runtime if needed.

User-defined memory regions can be parameterized individually. For instance, some regions may be set as read-only, while others may be set as non-executable (referred to as XN in ARM terminology), and so on.

Data memory is not shared between unprivileged tasks, but unprivileged tasks can use standard queues and semaphore mechanisms to pass messages to each other. Shared memory regions can be explicitly created using user-defined memory regions, but this is not recommended.

Privileged mode tasks can set themselves to unprivileged mode, but once in unprivileged mode, they cannot revert to privileged mode.

The FreeRTOS API is located in a region of Flash that can only be accessed when the microcontroller is in privileged mode (calling API functions temporarily switches to privileged mode).

Data maintained by the kernel is located in a region of RAM that can only be accessed while the microcontroller is in privileged mode.

System peripherals can only be accessed when the microcontroller is in privileged mode. Any code can access standard peripherals (UART, etc.), but can be explicitly protected using user-defined memory regions.

FreeRTOS-MPU can create two types of tasks:

Privileged tasks: Privileged tasks can access the entire memory map. Privileged tasks can be created using the xTaskCreate() or xTaskCreateRestricted() API functions.

Unprivileged tasks: Unprivileged tasks can only access their stack. Additionally, they can be granted access to up to three user-defined memory regions (three per task). Unprivileged tasks can only be created using xTaskCreateRestricted(). Note that xTaskCreate() cannot be used to create unprivileged tasks.

If a task wants to use the MPU, it must provide the following additional information:

🔹 The address of the task stack.

🔹 The start, size, and access parameters for up to three user-defined memory regions.

Consequently, the total number of parameters required to create a task is quite large. To simplify the creation of MPU tasks, xTaskCreateRestricted() uses a parameter structure called xTaskParameters, typically defined as a structure constant stored in Flash, and the address of this structure is passed as a single parameter to xTaskCreateRestricted().

typedef struct xTASK_PARAMTERS{   TaskFunction_t      pvTaskCode;   const signed char *   const pcName;   unsigned short      usStackDepth;   void *             pvParameters;   UBaseType_t       uxPriority;   portSTACK_TYPE * puxStackBuffer;   MemoryRegion_t    xRegions[ portNUM_CONFIGURABLE_REGIONS ];} TaskParameters_t;typedef struct xMEMORY_REGION{    void *pvBaseAddress;           /* Start address */    unsigned long ulLengthInBytes;   /* Length     */    unsigned long ulParameters;      /* Access attributes */} MemoryRegion_t;
Enhancing System Robustness with FreeRTOS MPU

The memory regions allocated to tasks can be modified using vTaskAllocateMPURegions().

Predefined Regions and User-Defined Regions

Regions 0-4 are configured by the kernel as available runtime environments, where:

🔸 Running tasks can access their own stack, but all other RAM can only be accessed when running in privileged mode.

🔸 The Flash memory region where the kernel and system peripherals reside can only be accessed when running in privileged mode.

🔸 Flash memory (except for the memory where the kernel resides) and all non-system peripherals (e.g., UART and analog inputs) can be accessed by both privileged and user mode tasks.

The kernel reconfigures the MPU during each context switch, allowing each task to define its remaining three regions differently.

Starting Address and Size Limitations of Regions

The MPU hardware imposes two rules that must be adhered to when defining starting addresses and sizes of regions:

1. The size of a region must be a power of 2 between 32 bytes and 4G (inclusive). For example, 32 bytes, 64 bytes, 128 bytes, 256 bytes, etc., are valid region sizes.

2. The starting address must be a multiple of the region size. For instance, a region configured to be 65536 bytes long must start from an address that is divisible by 65536.

FreeRTOS-MPU API

1. xTaskCreateRestricted() is an extended version of xTaskCreate() for creating tasks with restricted execution permissions or memory access permissions.

xTaskCreateRestricted() requires all parameters used by xTaskCreate(), plus four additional parameters to define three task-specific MPU regions and a stack buffer. Using this number of parameters in a regular function parameter list can be cumbersome and may consume a significant amount of stack space. xTaskCreateRestricted() takes a pointer to the xTaskParameters structure as one of its parameters, with the second parameter used to pass a handle to the created task, similar to the parameters of xTaskCreate(). If a task handle is not needed, pxCreatedTask can be set to NULL.

portBASE_TYPE xTaskCreateRestricted( xTaskParameters *pxTaskDefinition,

xTaskHandle *pxCreatedTask );

2. vTaskAllocateMPURegions() defines a set of Memory Protection Unit (MPU) regions for use by tasks restricted by the MPU. If MPU regions were not allocated at task creation, they can be reallocated at runtime using the vTaskAllocateMPURegions() function.

void vTaskAllocateMPURegions( TaskHandle_t xTaskToModify,

const MemoryRegion_t * const xRegions );

3. Privileged mode tasks can call portSWITCH_TO_USER_MODE() to set themselves to unprivileged mode. Tasks running in unprivileged mode cannot be set to privileged mode.

Follow our WeChat public account 【麦克泰技术】

Reply “segger free software” for download link

Reply “join group” to join the technical exchange group

Product Consultation:

Beijing: 010-62975900

Shanghai: 021-62127690

Shenzhen: 0755-82977971

Enhancing System Robustness with FreeRTOS MPU

Enjoy, look and like, at least I want to have one

Enhancing System Robustness with FreeRTOS MPU

Leave a Comment

×