MPU Enable But can not isolate memory access between tasks

static StackType_t xTaskStack[ 256 ] _attribute_((aligned(256*4)));

/* Declare an array that will be accessed by the task. The task should only
be able to read from the array, and not write to it. */
// ASILD_MEM_DATA volatile char cReadOnlyArray[32] _attribute_((aligned(32))) = {0};
// ASILD_MEM_DATA volatile char cReadWriteArray[32] _attribute_((aligned(32))) = {0};
static uint8_t cReadOnlyArray[32];
static uint8_t cReadWriteArray[32];
StaticTask_t pxTaskBuffer;

TaskHandle_t * pxCreatedTask = NULL;

void vTaskFunction(void* p) {

  myprintf("MPU_Task privilege = %s\\n",
         portIS_TASK_PRIVILEGED() ? "privileged" : "unprivileged");

//Verify read-only area
volatile uint32_t var = cReadOnlyArray\[0\];
myprintf("the read-only array value is %d\\n", var);


//Verify read-write area
cReadWriteArray\[0\] = 0XBB;
volatile uint32_t wr_var = cReadWriteArray\[0\];

/\*\* Verify whether the read-only area permission has been configured successfully\*/
// myprintf("start write read-only array value\\n");
// cReadOnlyArray\[0\] = 0XAA;
// myprintf("the read-only array value is %d\\n", cReadOnlyArray\[0\]);


vTaskDelete(NULL);

}

/* Fill in a TaskParameters_t structure to define the task - this is the
structure passed to the xTaskCreateRestricted() function. */
static const TaskParameters_t xTaskDefinition =
{
vTaskFunction, /* pvTaskCode */
“MPU_Task”, /* pcName */
256, /* usStackDepth - defined in words, not bytes. */
NULL, /* pvParameters */
(configMAX_PRIORITIES - 4), /* uxPriority - priority 1, start in User mode. */
xTaskStack, /* puxStackBuffer - the array to use as the task stack. */

/\* xRegions - In this case only one of the three user definable regions is
    actually used. The parameters are used to set the region to read only. \*/
{
    /\* Base address Length Parameters \*/
    { cReadOnlyArray, 32, tskMPU_REGION_READ_ONLY },
    { cReadWriteArray, 32, tskMPU_REGION_READ_WRITE },
},
&pxTaskBuffer

};

// then it must be aligned to ( 128 * 4 ) bytes. This example used GCC syntax. */
static StackType_t xTaskStack_two[ 256 ] _attribute_((aligned(256*4)));

/* Declare an array that will be accessed by the task. The task should only
be able to read from the array, and not write to it. */
ASILD_MONITOR_DATA volatile char cReadOnlyArray2[32] _attribute_((aligned(32))) = {0};
ASILD_MONITOR_DATA volatile char cReadWriteArray2[32] _attribute_((aligned(32))) = {0};

StaticTask_t pxTaskBuffer_two;

TaskHandle_t * pxCreatedTask_two = NULL;

//read cread or cwrite
void vTaskFunction_two(void* p) {

  myprintf("MPU_Task_two privilege = %s\\n",
         portIS_TASK_PRIVILEGED() ? "privileged" : "unprivileged");

//Verify read-only area
myprintf("=======start read other task xregion=====\\n");
volatile uint32_t var = cReadOnlyArray\[0\];
myprintf(" read other task read-only array value is %d\\n", var);


var = cReadWriteArray\[0\];
myprintf(" read other task creadwrite0 array value is %0x\\n", var);
//Verify read-write area
myprintf(" set other task creadwrite0 array value is 0XBC\\n");
cReadWriteArray\[0\] = 0XBC;
volatile uint32_t wr_var = cReadWriteArray\[0\];
myprintf(" read other task creadwrite0 value is %0x\\n", wr_var);

myprintf("=========== end read  other xregion task===============\\n");

/\*\* Verify whether the read-only area permission has been configured successfully\*/
// myprintf("start write read-only array value\\n");
// cReadOnlyArray\[0\] = 0XAA;
// myprintf("the read-only array value is %d\\n", cReadOnlyArray\[0\]);


vTaskDelete(NULL);

}

static const TaskParameters_t xTaskDefinition_two =
{
vTaskFunction_two, /* pvTaskCode */
“MPU_Task_two”, /* pcName */
256, /* usStackDepth - defined in words, not bytes. */
NULL, /* pvParameters */
(configMAX_PRIORITIES - 5), /* uxPriority - priority 1, start in User mode. */
xTaskStack_two, /* puxStackBuffer - the array to use as the task stack. */

/\* xRegions - In this case only one of the three user definable regions is
    actually used. The parameters are used to set the region to read only. \*/
{
    /\* Base address Length Parameters \*/
    { cReadOnlyArray2, 32, tskMPU_REGION_READ_ONLY },
    { cReadWriteArray2, 32, tskMPU_REGION_READ_WRITE },
},
&pxTaskBuffer_two

};

FUNC(void, OS_CODE)
StartupTask(void)
{
//LOG(DBG_INFO,“BSWInt”, “%s\r\n%s\r\n%s”, s_bst_man_name,s_bst_pro_name,s_bst_ver_string);
LOG(DBG_INFO,“SOCR5 BSWInt”, “%s”, s_bst_ver_info);

BaseType_t ret =  xTaskCreateRestrictedStatic(&xTaskDefinition, &pxCreatedTask);
if (ret != pdPASS) {
    myprintf("Failed to create restricted task\\n");
} else {
    myprintf("Successfully created restricted task\\n");
}


BaseType_t ret2 =  xTaskCreateRestrictedStatic(&xTaskDefinition_two, &pxCreatedTask_two);
if (ret2 != pdPASS) {
    myprintf("Failed to create restricted task2\\n");
} else {
    myprintf("Successfully created restricted task2\\n");
}


vTaskStartScheduler();

}

Hello, above is my test codes. I’d like to ask a few questions

  1. After porting the MPU function to cortexR5, it was found that non-privileged Task A and non-privileged Task B can access each other’s configured Xregions. Is there a problem with this?
  2. How should the code segment and data segment of Task A and Task B be isolated?
  3. Can FreeRtos MPU only isolate privileged and non-privileged memory?

I would need to check the specifications of the processor, but the regions for the MPU have a minimum granularity, and regions start at the beginning of that granularity, and span the full space of that block, which is likely larger than the 32 bytes in your code. The fact that your stack is aligned to a 1k boundary likely means that your other regions need to also be on a similar 1k boundary to define them for the MPU.

32 bytes is a common size of a cache line that matters for sharing between processors, not for MPU protection.

The hardware has the following 2 requirements:

  1. The size of a MPU region must be a power of 2 and minimum 32 bytes.
  2. The start address of a MPU region must be a multiple of its size.

Your declarations of cReadOnlyArray and cReadWriteArray do not have correct alignment.

Change the initialization of xRegions in TaskParameters_t to the following to ensure that uninitialized values are not used for region configuration:

{
    /* Base address Length Parameters */
    { cReadOnlyArray, 32, tskMPU_REGION_READ_ONLY },
    { cReadWriteArray, 32, tskMPU_REGION_READ_WRITE },
    { 0, 0, 0 }
}

This is true for text section. For the data section, an unprivileged task does not have access to any memory other than its stack and the ones you explicitly grant it access at the time of task creation (using xRegions member of TaskParameters_t).

Hello aggarg,

thanks for your answer. Another question:

We only need to isolate the text section for different ASIL tasks? And do we need to isolate the data, bss, stack, heap for different ASIL tasks?

PS: We use xTaskCreateRestrictedStatic to create different ASIL tasks.

Would you please help me understand what are you trying to achieve? Is it correct that you have a number of unprivileged tasks (ASIL tasks) that you are trying to isolate from each other.

How do you plan to do that? We grant unprivileged tasks access to unprivileged text using a MPU region - FreeRTOS-Kernel/portable/GCC/ARM_CM4_MPU/portmacro.h at main · FreeRTOS/FreeRTOS-Kernel · GitHub. Because the user configurable region are lower numbered, you wont be able to override it. The only option is to keep your tasks code outside the region covered by portUNPRIVILEGED_FLASH_REGION and then grant your unprivileged tasks explicit access to their respective code. You’ll also need to ensure that each region containing a task’s code satisfies MPU hardware requirements I mentioned above. Whether all that is worth it or not, would depend on what you are trying to achieve.

Hello aggarg,

Let me explain it.

ASIL tasks are some ISO26262 ASILD tasks, they have higher priority and higher task levels.

There are also some none ASIL tasks which have lower priority and lower task levels.

My target is none ASIL tasks can not impact ASIL tasks. One ASILD task can not impact anther ASILD task,include text section, data section, stack, heap and bss.

My draft solution is:

Put ASIL task related functions in privileged_functions section which in portPRIVILEGED_FLASH_REGION.

Put global and static initialized data in privileged_data section whic in portPRIVILEGED_RAM_REGION.

There are no un-initialized data, so no need to handle bss section.

There are no dynamic allocated data, so no need to handle heap section.

As you mentioned above, tasks can not access other’s stack, so no need to handle stack. Stack will be in its default section and MPU Region.

For none ASILD tasks, do nothing for them, just keep default.

Is the above draft make sense? Thanks.

Another questions:

  1. If ASILD task’s stack and none ASILD task’s stack are put in the same MPU region, can none ASIL task access ASIL task’s stack?
  2. Can i put ASIL task’s stack in privileged_data section which in portPRIVILEGED_RAM_REGION. It means stack in the same section with global/static initialized data.

Does this mean that you are creating ASIL tasks as privileged tasks? If yes, this should be okay.

No, they are not put in the same MPU region.

Again, if ASIL task is privileged, then yes.

Hello aggarg,

Thanks a lot for you answer, it is clear now.

One more question :slight_smile: .

QueueHandle_t MPU_xQueueCreateCountingSemaphoreStatic( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount, StaticQueue_t * pxStaticQueue ) PRIVILEGED_FUNCTION;

In mpu_wrapper.c, its source code is:

#if ( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) )

    QueueHandle_t MPU_xQueueCreateCountingSemaphoreStatic( const UBaseType_t uxMaxCount,
                                                           const UBaseType_t uxInitialCount,
                                                           StaticQueue_t \* pxStaticQueue ) /\* FREERTOS_SYSTEM_CALL \*/
    {
        QueueHandle_t xReturn;

        if( portIS_PRIVILEGED() == pdFALSE )
        {
            portRAISE_PRIVILEGE();
            portMEMORY_BARRIER();

            xReturn = xQueueCreateCountingSemaphoreStatic( uxMaxCount, uxInitialCount, pxStaticQueue );
            portMEMORY_BARRIER();

            portRESET_PRIVILEGE();
            portMEMORY_BARRIER();
        }
        else
        {
            xReturn = xQueueCreateCountingSemaphoreStatic( uxMaxCount, uxInitialCount, pxStaticQueue );
        }

        return xReturn;
    }

It was put into FREERTOS_SYSTEM_CALL section which in none PRIVILEGED mpu region.

When a none ASIL task call this function, it will first raise PRIVILEGE, do job, then call reset PRIVILEGE. It works ok.

But in mpu_wrapper_v2.c:

#if ( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) )

    QueueHandle_t MPU_xQueueCreateCountingSemaphoreStatic( const UBaseType_t uxMaxCount,
                                                           const UBaseType_t uxInitialCount,
                                                           StaticQueue_t \* pxStaticQueue ) /\* PRIVILEGED_FUNCTION \*/
    {
        QueueHandle_t xInternalQueueHandle = NULL;
        QueueHandle_t xExternalQueueHandle = NULL;
        int32_t lIndex;

        lIndex = MPU_GetFreeIndexInKernelObjectPool();

        if( lIndex != -1 )
        {
            xInternalQueueHandle = xQueueCreateCountingSemaphoreStatic( uxMaxCount, uxInitialCount, pxStaticQueue );

            if( xInternalQueueHandle != NULL )
            {
                MPU_StoreQueueHandleAtIndex( lIndex, xInternalQueueHandle );
                xExternalQueueHandle = ( QueueHandle_t ) CONVERT_TO_EXTERNAL_INDEX( lIndex );
            }
            else
            {
                MPU_SetIndexFreeInKernelObjectPool( lIndex );
            }
        }

        return xExternalQueueHandle;
    }

It was implemented as PRIVILEGED_FUNCTION, which was put into PRIVILEGED MPU REGION. When a none ASIL task call this function will lead to a prefetch abort.

We are using mpu wrapper v2, could you please give some advice about this issue?

Thanks.

MPU Wrapper v2 is a new security enhancement that introduces multiple security improvements. When using MPU Wrapper v2, unprivileged tasks are restricted from creating FreeRTOS objects. Instead, all objects must be created either before starting the the scheduler or in privileged tasks.

For detailed information about the security considerations and threat model, please visit: Kernel threat model - FreeRTOS™.

Thank you aggarg, another question:

In above reply, you have mentioned:

For the data section, an unprivileged task does not have access to any memory other than its stack and the ones you explicitly grant it access at the time of task creation (using xRegions member of TaskParameters_t).

  1. unprivileged task run in user mode, right?
  2. unprivileged task can not access heap which means it can not call malloc and portMalloc, can not access data in heap, global data in BSS section?

Yes.

The answer depends on which FreeRTOS heap you are using. Assuming that you are using heap_4, an unprivileged task cannot call or access memory allocated by pvPortMalloc. malloc is provided by your c-library and whether or not an unprivileged task can access it, will depend on where the function is placed and where it is allocating memory from. Why do you need to use malloc though when you can use pvPortMalloc?

For the global data and BSS, an unprivileged task cannot access them by default by you can grant explicit access.

hello aggarg,

Thank you very much for all above reply. One more question:

I saw there is a fucntion: xPortIsAuthorizedToAccessBuffer,

This function will return whether a task can access a buffer area.

We know that all mpu regions have been configured and can protect ram access.

So what is the usage scenario and function of this function?

I think it so the kernel can see if the current task that called a kernel function should be allowed to access the memory in question.

thank you for your reply, richard.
I have got some information as below:

  1. System Call Parameter Validation

When tasks pass buffer pointers to the kernel through system calls (such as message queues, semaphores, task notifications, etc.), the kernel needs to verify whether the buffer belongs to a valid memory region of the calling task.

  1. 2.Prevention of Buffer Overflow Attacks

During inter-task communication, prevent malicious tasks from passing a buffer address that belongs to another task or the kernel.

  1. 3.Dynamic Memory Allocation Verification

In secure dynamic memory allocators, verify whether the returned memory block falls within the permitted region of the calling task.

@Allenhe123 That is the correct explanation. In other words, we need to validate that an unprivileged task is authorized to access a buffer it supplies to a system call. System call implementations run with privileges and therefore, we cannot rely on MPU configuration to prevent writes to unauthorized areas.

Got it. Thank you very much, aggarg :slight_smile: