Memory Protection
What is Memory Protection?
Memory protection isolates programs so they can’t access each other’s memory. Each program runs in its own protected space with hardware-enforced boundaries.
CMRX enables memory protection on microcontrollers, which traditionally run all code in a single memory space without isolation or provide partition-level isolation that requires high involvement of developers and is error prone.
All mechanisms described below are configured and maintained fully automatically. There is no need of developer to explicitly configure memory protection hardware.
Process Isolation
CMRX implements memory isolation using the concept of processes. Each process gets its own memory regions:
- Data section: Stores variables and data structures. Data can never be executed.
- Stack section: Used for function calls and local variables, private to each thread.
Code section is common to all the code. This enables use of shared code and simplifies integration of SDKs. Only regions marked as code can be executed. This prevents many buffer overflow-based attacks which inject code into remotely-writable buffers and then manipulate software to execute it. This is not possible in CMRX as all writable regions are marked as non-executable.
The ARM Cortex-M Memory Protection Unit (MPU) enforces these boundaries in hardware. Therefore this mechanism is reliable and generates very low overhead. All the developer has to do is specify one line of code:
OS_APPLICATION(my_process);
This will tell the build system that all code residing in the static library together with file containing the line above can access all data residing in the same library. Everything else is off-limits. No need to manually configure regions.
Bonus feature provided by the memory protection is stack overflow protection for every thread. While stacks reside in process’ memory area, each stack is protected against overflow and underflow or access from thread different than thread which owns the stack. This drastically speeds up stack overflow detection.
Driver Protection
Unlike most of real-time operating systems, device drivers in CMRX are neither hosted inside kernel nor run in privileged mode. Drivers run as separate processes and are a subject of memory protection rules too, so they can only access memory which they were granted permissons to. This prevents driver bugs from silently affecting other parts of the system.
The only difference between a process and a driver in CMRX is that driver is explicitly given access to some memory area where peripheral is mapped:
OS_APPLICATION_MMIO_RANGES(uart_driver, 0x40004400, 0x40004800);
This gives the uart_driver process (defined by OS_APPLICATION macro) read/write access to memory ragion where peripheral registers are mapped. Again, no need to manually define protection regions.
Thus the driver can operate this peripheral by reading and/or writing from/to this region. Yet the driver can’t access other unrelated peripherals nor damage memory of other ordinary processes in the system or kernel memory nor can execute data residing in peripheral memory, such as FIFO buffers.
This mitigates possibility of attacks targeted at bugs in drivers. In cases where drivers are run in privileged modes, these bugs are high-value targets as they provide easy entry into system. Drivers, that are memory protected make effective exploitation of vulnerability much harder as some possible ways to exploit existing vulnerabilities are completely avoided (such as executing code injected into driver buffers via buffer overflow) and other are made impractical (corrupting kernel data structures via buffer overflow in driver).
Automatic Violation Handling
When a process or a driver tries to access forbidden memory MPU detects it and raises fault. System can then handle this situation.
Technical Requirements
Hardware:
- ARM Cortex-M0+, Cortex-M3, Cortex-M4 or Cortex-M7 processor
- Memory Protection Unit (MPU) present
- Minimum 8 MPU regions