NULL pointer protection with ARM Cortex-M MPU

782 Views Asked by At

The MPU in ARM Cortex-M (M0+/M3/M4/M7/etc.) is often advertised as allowing to set up protection against dereferencing the NULL pointer. But how to do this in practice? (Some online discussions, like in the Zephyr Project, indicate that the issue is not quite trivial.)

I'm looking for the simplest possible MPU code running in "Privileged mode" on bare-metal ARM Cortex-M. Please note that "protection against dereferencing the NULL pointer" means to me protection both against reads and writes. Also, it is not just about the address 0x0, but small offsets from it as well. For example, accessing a struct member via a NULL pointer should also cause MPU exception:

struct foo {
    . . .
    uint8_t x;
};
. . .
uint8_t x = (*(struct foo volatile *)NULL)->x; // should fail!
2

There are 2 best solutions below

0
On BEST ANSWER

After some experimentation, I've come up with the MPU setting that seems to work for most ARM Cortex-M MCUs. Here is the code (using the CMSIS):

/* Configure the MPU to prevent NULL-pointer dereferencing ... */
MPU->RBAR = 0x0U                          /* base address (NULL) */
            | MPU_RBAR_VALID_Msk          /* valid region */
            | (MPU_RBAR_REGION_Msk & 7U); /* region #7 */
MPU->RASR = (7U << MPU_RASR_SIZE_Pos)     /* 2^(7+1) region, see NOTE0 */
            | (0x0U << MPU_RASR_AP_Pos)   /* no-access region */
            | MPU_RASR_ENABLE_Msk;        /* region enable */

MPU->CTRL = MPU_CTRL_PRIVDEFENA_Msk       /* enable background region */
            | MPU_CTRL_ENABLE_Msk;        /* enable the MPU */
__ISB();
__DSB();

This code sets up a no-access MPU region #7 around the address 0x0 (any other MPU region will do as well). This works even for the MCUs, where the Vector Table also resides at address 0x0. Apparently, the MPU does not check access to the region by instructions other than LDR/STR, such as reading the vector address during Cortex-M exception entry.

However, in case the Vector Table resides at 0, the size of the no-access region must not contain any data that the CPU would legitimately read with the LDR instruction. This means that the size of the no-access region should be about the size of the Vector Table. In the code above, the size is set to 2^(7+1)==256 bytes, which should be fine even for relatively small vector tables.

The code above works also for MCUs that automatically relocate the Vector Table, such as STM32. For these MCUs, the size of the no-access region can be increased all the way to the relocated Vector Table, like 0x0800'0000 in the case of STM32. (You could set the size to 2^(26+1)==0x0800'0000).

Protection against NULL-pointer dereferencing is an important tool for improving the system's robustness and even for preventing malicious attacks. I hope that this answer will help fellow embedded developers.

0
On

For Cortex-m Family you can use MPU in case Cortex-M was synthetized with MPU unit. Below a snooped code can be used to protect 256 byte starting from address 0 (0x00000000 - 0x00000100)

// check if MPU is implemneted before enabling it
if ( MPU->TYPE != 0 )
{
  // set MPU configuration to protect first 256 bytes in rom from write 
   access
  MPU->CTRL = 0x0; // disable MPU                   
  MPU->RNR  = 0x0; // define region number 0 to protect first 256 bytes in rom                 
  MPU->RBAR = 0x00; // base address to protect is rom starting at address 0                  
  MPU->RASR = 0x0000000F;  // protect first 256 bytes of rom against write/read access  
  MPU->CTRL = 0x5; // enable MPU and define background memory ma for not protected area
  __DSB();
  __ISB();  
}

an MPU stands for Memory Protection Unit which allow changing the memory attribute at runtime (switching between strongly ordered/memory/device attribute), setting cacheability for targeted memory region by MPU configuration, enabling/disabling basic read/write/execute access to memroy region. The miniumum granularity here will be 32 bytes, in above code we decided to protect up 256 bytes and MPU here was configured to prevent read/write access to address rnager 0 - 0x100 (256 bytes)

this will protect against NULL pointer access for sure however the question that may come to mind is if this will cause problems when having an interrupt, the point is in arm cortexm the vector table should be located at address 0 because after reset the cortex m will boot from address 0 ,means it will fetch stack pointer from address 0 and reset handler address from address 0x4 and if we are protecting the first 256 bytes against read attempt you may thing this will block reading the address of any interrupt handler, however this protection is against read/write attempt when cortex-m execute LDR/STR instruction but for case fetching address by hardware this is not true.

256 bytes will be the max size of our vector table so anything beyond that will be access by the processor because we may have some const data that need to be read from that section of code so we must not enable MPU protection on such section.

Note that the Above MPU rpoection will work for Cortex-M3/Cortex-M4/Cortex-M7 and Cortexm0+ in case MPU is present, for Cortex-M33/M23 (armv8m) it will not work as the MPU configuration for v8m architecture is different from armv7m architecture.