Introducing a new mechanism to detect and mitigate memory corruption in MCU-based applications.
Embedded software developers working at the real-time operating system (RTOS) level know that memory corruption issues are easy to introduce, difficult to detect, and often devasting to application safety and security. This makes preventing these issues a popular subject among providers of industry standards, test tools, and RTOS solutions. Despite this, there remain significant constraints in mitigating memory corruption on microcontroller unit (MCU) devices as they are unable to support the more sophisticated programming and RTOS techniques.
At the core of billions of applications worldwide, MCUs present unique safety and security challenges. With no memory management unit (MMU) or memory protection unit (MPU), and with little capacity for including memory protection features in code, MCUs provide embedded developers few options for ensuring the robustness of their systems.
As MCU deployments continue to rise and support more connected systems, developers need better methods to protect against memory corruption issues without compromising the strict functional and performance requirements of these hard real-time systems.
Challenges in safety and security for MCU-based applications
To minimize processor workloads and memory footprints, MCU-based applications tend to run within a single address space that is shared globally across all threads. Unlike applications running on a higher-level RTOS—such as Embedded Linux—that partitions memory into different address spaces, MCU applications have access to the entire range of memory. This monolithic approach puts the burden on developers to find their own ways of preventing application functions from corrupting memory outside their own segments.
Corruption occurs when data inside a memory location is changed through unexpected or undesirable means, such as the alteration of a pointer from a valid to suspicious address. For example, the corruption of function pointers could lead to program execution jumping to an invalid memory location, causing a system failure. If hackers suspect this weakness exists, they can exploit the unprotected pointer to misdirect execution to malicious code they have introduced through a technique called code injection.
Protecting against memory corruption requires both foresight and knowledge. Developers need to recognize when an issue may occur (not always easy in applications with multiple threads accessing the same memory locations) and know how to minimize its probability of occurrence. Here are some typical remediation strategies for devices without built-in memory protection:
These techniques consume device resources and rely on the developer remembering where to use them. The latter two also require considerable effort in planning, architecture, and testing.
The PX5 RTOS, designed for demanding MCU-based applications, offers a built-in approach to memory protection that minimizes developer and device overhead. This approach is called Pointer Data Verification (PDV).
Mitigating memory corruption with PDV
PDV is a lean and robust method of memory address verification unique to the PX5 RTOS. Designed specifically for resource-constrained MCU applications that lack access to an MMU or MPU, PDV is a software-only technique that creates and stores a unique verification code for memory structures to help applications avoid unexpected and unauthorized accesses.
Once a developer turns on PDV, the PX5 RTOS automatically creates verification codes upon loading values to sensitive data locations. Before the application uses the sensitive data, the RTOS generates the verification code again and compares it with the stored value. If these two codes fail to match, the PX5 RTOS calls a central error handling function that executes actions specified by the developer.
Developers can define the formula for generating the verification code or use the default mechanism provided by the PX5 RTOS. The default verification code is a combination of a secret runtime identification passed to the RTOS (such as the result from a true random number generator), the value of the sensitive data, and the address to store the generated code. The default formula looks similar to this: Verification Code = ((Data Value) + (Address to Store Code) + (Secret)) ^ (Secret)
How developers use PDV
One example of how developers can use PDV to protect sensitive data structures is that of Function Pointer Verification (FPV). When building an application with the PX5 RTOS, developers can use the PX5_FUNCTION_POINTER_VERIFY_ENABLE flag to ensure verification of all function pointers before their use. Take this example of creating an application’s start routine function pointer using the POSIX Threads (pthreads) API supported by the PX5 RTOS:
Let’s say the compiler allocates memory as specified in Table 1. Let’s further state that the computed verification code for the entry_routine function pointer is 0x9D919C7D. Figure 1 shows the thread control block after thread creation, both in raw memory format and as a data watch.
When the start_routine() function pointer is used during runtime, the PX5 RTOS recalculates the verification code and compares it with the stored verification code, 0x9D919C7D. If the newly computed code matches the stored code, the function pointer is considered valid and the function start_routine() is called. If the codes fail to match, there is a high likelihood of memory corruption in either the function pointer or the stored verification code, so the PX5 RTOS calls the central error handling function.
Through this approach, a hacker attempting to remotely alter program execution would fail as their injected code is unable to generate a matching verification code.
Protecting against memory corruption from the ground up
PDV is patent-pending technology that provides developers with robust detection and mitigation of memory corruption issues without compromising application performance or resource budgets. The PX5 RTOS itself is ultra-small (less than 1 KB for minimal use), ultra-portable (with a fully compliant pthreads API), and rigorously tested (100% C statement and branch decision coverage for every release), making it the ideal foundation for systems requiring robust determinism, safety, and security.
Although PDV is designed for MCU environments without memory protection hardware, it can also supplement code safety and security practices for more robust processors having MPU and/or MMU capabilities.