By Bill Lamie
President/CEO, PX5
These days we are all a bit insecure when it comes to embedded security. It’s a fast-moving area with new threats emerging all the time. Worst of all, we in embedded software development typically live in a resource-constrained hardware world, which is at odds with most embedded security defense mechanisms.
To further complicate things, embedded security requirements vary greatly depending on the importance of what needs to be protected, the potential attack surface, and what is technically and economically practical. Stated another way, embedded security is more of a spectrum rather than a one-size-fits-all approach. For example, the security requirements of a bank are vastly different than the security requirements of a restaurant. The same holds in embedded systems.
Embedded processors offer a variety of embedded security features. Let’s take a look at the most common.
Anti-tampering. This feature shields the firmware from unauthorized access. This is used to protect the firmware IP and prevent adversaries from studying the object code for vulnerabilities.
Lock-step execution. This security feature employs multiple processors executing the same code with the same data, thus guaranteeing exact code execution. Such hardware is mostly found in safety-critical applications, and safer code is generally more secure.
Anti-glitch. This security feature employs circuitry to prevent an attacker from causing abnormal program execution via manipulating power or other system signals. Otherwise, an attacker might be able to “skip” the instruction that enables MMU or MPU setup.
Execute only from flash. Most microcontrollers (MCUs) execute instructions from flash. Some of these MCUs can also prohibit execution from RAM, which is recommended to help prevent dynamic insertion of malicious code in remote execution attacks.
Hardware stack limit. Some processors have a stack limit register that guards against memory corruption caused by stack overflow.
Hardware watchdog timer. Many processors have a non-maskable hardware watchdog timer. This security feature acts as a fail-safe. Under normal operation, the application code resets the watchdog on a regular basis and always before its expiration. During abnormal execution, the watchdog is not reset, thus leading to a non-maskable interrupt that halts the abnormal execution. Typically, applications will simply reset after a watchdog expiration.
True random number generator (TRNG). Having a TRNG or even more basic random number generator (RNG) in hardware is very beneficial. This is most important for embedded devices that are networked.
Memory management unit (MMU). Typically, available only on larger, more powerful processors, this hardware security feature can restrict access to various regions of memory. This is most often set up once by the application firmware after reset to map and protect access to various memory regions.
Memory protection unit (MPU). This hardware security feature is similar to the MMU and is found in smaller, more resource-constrained devices. Again, this is typically set up by the application firmware after reset to protect various memory regions. In cases where there are no stack limit registers, the MPU can set up a protected block at the top of each thread’s stack to prevent stack overflow.
Secure element (SE) or trusted platform module (TPM). For networked embedded devices, an SE or TMP can securely isolate credentials or other secrets from the main application. This can greatly increase the network security of the device and is therefore highly recommended.
Of course, each of the hardware security features mentioned have an associated cost–in circuitry, power consumption, size, etc. These costs must go through a risk-benefit analysis.
Regardless of the hardware security features you have at your disposal, there also things you can do in your software to help improve embedded security. Here are some of the best practices in software that can improve your device’s security.
Harden the device firmware. You should strive for 100% statement and 100% branch/decision coverage testing, since safer code is also more secure.
Leverage static analysis and related tools. In addition to hardening, it’s a good idea to leverage static analysis tools as well as penetration testing and fuzzing tools. These security testing tools help find subtle issues in development, which is much easier than debugging fielded devices.
Use an adequate (or larger) stack size. Stack overflow is the number one cause of memory corruption in embedded systems. Each thread stack must have enough memory for its worst-case function call nesting – including all local variables in each function. If not, the stack will overflow into the memory directly preceding the stack. This security problem can be prevented by using hardware stack limit features or, alternatively, the MPU/MMU to guard the area directly above the stack.
Explicitly specify and check buffer sizes. In all functions where a buffer is supplied, it is important for the caller to explicitly provide the size of the buffer and the called to explicitly check the size to avoid overrun.
Be mindful of memory corruption probabilities. When a thread stack overflows, it generally corrupts the memory immediately preceding the thread stack memory (in most architectures, thread stack grows toward lower addresses). In contrast, buffer overflows are more likely to corrupt memory immediately following the buffer. Therefore, it is generally safer to place critical data after stacks and before buffer memory.
Insert run-time and location unique data patterns between stacks and buffers that can be used to detect memory corruption during run-time.
Be extremely careful with function pointers. Function pointers provide an easy path to unwanted program execution – both unintentional and intentional. For example, it’s not good practice to place function pointers inside buffers since a buffer overflow could also overwrite the function pointer. It’s also good to verify function pointers before they are used via a small hash or checksum. Function pointer corruption represents the easiest way for an attacker to initiate unwanted remote execution, rendering your embedded security insecure.
Utilizing your hardware security resources as well as following some best practices in embedded software security will help improve your the security of your embedded devices. Also remember that all embedded security measures – on the device side and on the network level where your device lives – are all part of an overall defense-in-depth strategy. The sole goal of embedded security being to make it more difficult for adversaries to get unwanted access!