ARMv8-M MPU Access Control Limitations and NULL Pointer Protection

The ARMv8-M architecture introduces a Memory Protection Unit (MPU) that is designed to provide memory access control for embedded systems. Unlike its predecessor, the ARMv7-M MPU, the ARMv8-M MPU simplifies the access permission (AP) field to two bits, which limits the granularity of access control configurations. This change has raised questions about how to implement specific memory protection strategies, such as marking a region as completely inaccessible to enforce NULL pointer checks. In ARMv7-M, the MPU’s AP field included three bits, allowing developers to explicitly set a region to "no access" by configuring the AP field to 000. However, the ARMv8-M MPU’s two-bit AP field does not provide a direct equivalent to this configuration.

The primary use case for marking a region as inaccessible is to catch NULL pointer dereferences, which are a common source of software bugs. By configuring the memory region at address 0x00000000 (or another specific range) as inaccessible, any attempt to read from or write to this region will trigger a memory fault. This fault can then be handled by the system, either by logging the error, resetting the system, or taking other corrective actions. The challenge in ARMv8-M lies in achieving this behavior given the reduced flexibility of the AP field.

The ARMv8-M MPU’s AP field offers the following access permission configurations:

  • 00: Read/write by privileged code only.
  • 01: Read/write by any privilege level.
  • 10: Read-only by privileged code only.
  • 11: Read-only by any privilege level.

As evident, none of these configurations explicitly deny all access to a memory region. This limitation necessitates a different approach to achieve the desired behavior of marking a region as completely inaccessible.

PRIVDEFENA Bit and Unmapped Memory Regions

The solution to this problem lies in understanding the behavior of the ARMv8-M MPU when memory regions are left unmapped. The MPU_CTRL register in ARMv8-M includes a bit called PRIVDEFENA (Privileged Default Enable). When this bit is set, the MPU allows privileged code to access any memory region that is not explicitly mapped by an MPU region. Conversely, when PRIVDEFENA is cleared, any access to an unmapped memory region—whether by privileged or unprivileged code—will trigger a memory fault.

This behavior provides a mechanism to enforce no-access regions without explicitly configuring the AP field. By clearing the PRIVDEFENA bit and carefully mapping only the memory regions that are required for the application, any access to unmapped regions will result in a fault. This approach effectively achieves the same outcome as marking a region as inaccessible in ARMv7-M, albeit through a different mechanism.

For example, to protect the memory region from 0x00000000 to 0x00001000 (4 KB) as a no-access region for NULL pointer checks, the following steps can be taken:

  1. Clear the PRIVDEFENA bit in the MPU_CTRL register.
  2. Define MPU regions for all memory areas that the application needs to access, such as SRAM, peripherals, and stack space.
  3. Leave the region from 0x00000000 to 0x00001000 unmapped.

Any attempt to access the unmapped region will trigger a memory fault, providing the desired protection against NULL pointer dereferences.

Implementing No-Access Regions with ARMv8-M MPU

To implement no-access regions using the ARMv8-M MPU, follow these detailed steps:

Step 1: Disable the MPU

Before configuring the MPU, it must be temporarily disabled to avoid unintended behavior during the setup process. This is done by clearing the ENABLE bit in the MPU_CTRL register.

LDR R0, =MPU_CTRL
LDR R1, [R0]
BIC R1, R1, #1 ; Clear the ENABLE bit
STR R1, [R0]

Step 2: Clear the PRIVDEFENA Bit

Next, clear the PRIVDEFENA bit in the MPU_CTRL register to ensure that any access to unmapped memory regions triggers a fault.

LDR R0, =MPU_CTRL
LDR R1, [R0]
BIC R1, R1, #4 ; Clear the PRIVDEFENA bit
STR R1, [R0]

Step 3: Define Required MPU Regions

Map all memory regions that the application needs to access. This typically includes SRAM, flash memory, peripheral registers, and stack space. Each MPU region is defined using the MPU_RBAR (Region Base Address Register) and MPU_RLAR (Region Limit Address Register).

For example, to map a 64 KB SRAM region starting at 0x20000000:

LDR R0, =MPU_RBAR
LDR R1, =0x20000000 ; Base address
ORR R1, R1, #0x10 ; Region number (e.g., region 1)
STR R1, [R0]

LDR R0, =MPU_RLAR
LDR R1, =0x2000FFFF ; Limit address
ORR R1, R1, #0x1 ; Enable region
STR R1, [R0]

Repeat this process for all required memory regions, ensuring that the region from 0x00000000 to 0x00001000 remains unmapped.

Step 4: Enable the MPU

Once all regions are configured, re-enable the MPU by setting the ENABLE bit in the MPU_CTRL register.

LDR R0, =MPU_CTRL
LDR R1, [R0]
ORR R1, R1, #1 ; Set the ENABLE bit
STR R1, [R0]

Step 5: Handle Memory Faults

Configure the system to handle memory faults triggered by accesses to unmapped regions. This typically involves setting up a fault handler in the vector table and implementing logic to log the fault, reset the system, or take other appropriate actions.

void MemManage_Handler(void) {
    // Log fault information
    // Reset system or take corrective action
}

Verification and Testing

After implementing the no-access region, thoroughly test the system to ensure that:

  1. Accesses to the protected region (e.g., 0x00000000) trigger a memory fault.
  2. Accesses to mapped regions function as expected.
  3. The fault handler correctly processes memory faults.

By following these steps, developers can effectively enforce no-access regions on ARMv8-M systems, providing robust protection against NULL pointer dereferences and other memory access violations. This approach leverages the ARMv8-M MPU’s behavior to achieve the desired outcome despite the reduced flexibility of the AP field.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *