1

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!
artless noise
  • 21,212
  • 6
  • 68
  • 105
Miro Samek
  • 1,909
  • 14
  • 19
  • What you show ax example of small offset from `0`, actually *is* a null-pointer dereference, since you dereference the null pointer *first* before accessing the `x` member. – Some programmer dude Nov 23 '22 at 16:23
  • 1
    Also note that on many bare-metal systems address `0` might actually be a perfectly valid address (might be where RAM starts for example). – Some programmer dude Nov 23 '22 at 16:24
  • 1
    Address 0 in ARM cores can be mapped to flash, RAM or external memory so address 0 is perfectly fine. You need to configure MPU to disallow any accesses to/from address 0. – 0___________ Nov 23 '22 at 16:31
  • @Some-programmer-dude: Yes, absolutely, the address 0x0 is part of the problem and a big part of my question. In ARM Cortex-M address 0x0 is where the vector table starts. – Miro Samek Nov 23 '22 at 16:33
  • @MiroSamek it can be or not. Vector table address is settable. Depending on the model you can even have this stored in option bytes as well and loaded during the boot. – 0___________ Nov 23 '22 at 16:34
  • @0___________: Yes, I'm aware that the vector table can be relocated in Cortex-M. Is that part of the solution? I'm looking for a more complete answer... – Miro Samek Nov 23 '22 at 16:36
  • @Some-programmer-dude: regarding the NULL pointer dereferencing in my "struct foo" example. The CPU does not see the null-pointer dereference. Instead, the ARM CPU sees a small non-zero offset (the offset of member 'x' in struct foo). – Miro Samek Nov 23 '22 at 16:39
  • "In ARMv7-M, software can run either at privileged or unprivileged level. In systems implemented with the ARMv6-M base architecture, all software runs at privileged level." – old_timer Nov 23 '22 at 16:44
  • so the "I'm looking for the simplest possible MPU code running in "Privileged mode" on bare-metal ARM Cortex-M." would be "b ." for m0, m0+ – old_timer Nov 23 '22 at 16:44
  • @MiroSamek did you read the documentation or you take your knowledge from zephyr forums? – 0___________ Nov 23 '22 at 16:45
  • @0___________: Yes, I did my homework. I read the MPU documentation, the Zephyr discussion, and many other things. I did some experiments as well. But I somehow failed to get a working implementation. That's why I've asked on stack-overflow... – Miro Samek Nov 23 '22 at 16:47
  • "All exceptions execute as privileged code" so b . is also the simplest possible mpu code for m3,4,7. – old_timer Nov 23 '22 at 16:52
  • "is often advertised as allowing to set up protection against dereferencing the NULL pointer" please provide references to this advertisement I only see one instance of the word NULL and it is for NULL termination of a specific table. There is an endless supply of privileged vs unprivileged access references though. Which as clearly indicated in comments above you need to setup yourself not have it magically happen. – old_timer Nov 23 '22 at 16:54
  • Please read the arm documentation, write some code, if it does not work then post a minimal example here and ask questions about that code...If you have code already please provide it. For what you are asking for I would say maybe a dozen to a few dozen lines of asm is the max that would be required to setup the mode and test it. – old_timer Nov 23 '22 at 16:56
  • @old_timer: the NULL pointer protection with MPU is advertised, for example, in the blog post: https://interrupt.memfault.com/blog/fix-bugs-and-secure-firmware-with-the-mpu . The blog presents a solution, where the Flash ROM is setup as read-only region in the MPU. This protects against *writes* to the NULL pointer, but does NOT protect against the reads from NULL pointer. – Miro Samek Nov 23 '22 at 17:36
  • Chris Coleman is a founder and CTO at Memfault. Prior to founding Memfault, Chris worked on the embedded software teams at Sun, Pebble, and Fitbit. – old_timer Nov 23 '22 at 19:22
  • Doesnt work for arm. I assume that memfault is not an advertising agency for arm – old_timer Nov 23 '22 at 19:23
  • everything you want to know is in the arm documentation. we are not here to read the docs for you and write code for you, this is not how the site works. you said you wrote some code that didnt work, that is what this site is about. – old_timer Nov 23 '22 at 19:26
  • Wouldn't you first have to relocate the vector table before setting up the MPU? – Lundin Nov 24 '22 at 08:57
  • @Lundin: The need to relocate the vector table is part of my question. Do you know for sure that relocation is necessary? – Miro Samek Nov 24 '22 at 14:06
  • No, I don't. Although I guess Cortex M doesn't have any built-in check for data access of read-only program flash memory, so maybe an attempt to access address zero has no side effects at all. – Lundin Nov 24 '22 at 14:18
  • Normally, Cortex-M reads from address 0x0 without any problems. This is because that's where the vector table is located (at least initially). – Miro Samek Nov 24 '22 at 15:09
  • @MiroSamek Yeah indeed, I think I've seen some CRT which reads from there even though the MSP is loaded through hardware out of reset (maybe it read the PSP?). And if you attempt to write, I suppose nothing at all will happen since it's a flash location. – Lundin Nov 24 '22 at 15:40
  • @Lundin Yes, attempts to read from address 0x0 (and above) are completely fine. Attempts to write can be prevented by the MPU as a side effect of making the whole flash ROM read-only, which is standard practice. So in the essence, this whole question is about preventing the *read* access to address 0x0 and slightly above. – Miro Samek Nov 24 '22 at 16:49

2 Answers2

2

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.

Miro Samek
  • 1,909
  • 14
  • 19
0

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.

dhokar.w
  • 386
  • 3
  • 6