We live in a world that depends on embedded software. It’s in the cars we drive, the elevators we use and the planes we travel in. As these systems become increasingly complex, the security and functionality of embedded software systems is becoming integral to software development. However, due to the nature of embedded systems, many traditional testing methods fall short of providing adequate security for them. In this article, we will explore some of the challenges of embedded software security testing and look into testing approaches that can address them.
Table of Contents
- Hardware Dependencies
- Hardware-agnostic testing
- Wide Range of Attack Vectors
- Shift-left Testing
- The "Security vs Safety" Tradeoff
- Manual Regression Testing
- Testing Under Time and Resource Constraints
What Makes Embedded Software Testing So Complex?
Although many embedded systems are already designed with security in mind, it’s a no-brainer that no system can be 100% secure. Many attack vectors can be exploited in embedded software, such as Buffer Overflows, Memory Corruption, and Code Injection. A successful attack can result in the compromise of the device, data theft, or remote control. Embedded systems must also be fail-safe, especially in automotive, medical devices, aviation, and other areas where human lives depend on their functionality.
7 Challenges of Embedded Software Security Testing
For the reasons described above, identifying potential vulnerabilities in embedded systems is essential. However, testing them can be quite challenging due to the unique interaction of hardware and software within embedded systems. Here are 7 of the main challenges of embedded software security testing.
1. Hardware Dependencies and Limited Hardware Access
Embedded software tests examine whether the final product, including hardware and software, fulfills all functional and non-functional requirements. Usually, the tester performs so-called system or end-to-end tests manually. Testing is often conducted using remote servers as a workaround since testing teams do not always have direct access to the hardware.
If you want to test separate modules or units earlier, you have to simulate or mock hardware dependencies. This can be challenging, as you usually have to do it manually.
We recommend mocking or mock-testing your embedded systems with fuzz data. This allows you to dynamically generate return values for your mocked functions, simulating the behavior of external sources under realistic conditions. You can use automated tools like CI Spark by Code Intelligence to generate mocks for external dependencies automatically.
2. Hardware-agnostic testing
When your software has to work on various hardware, you need to be able to simulate all these types of platforms to test its behavior.
Mocking is one of the best alternative strategies but requires significant manual overheads. If you use CI Spark by Code Intelligence to mock hardware dependencies, you can simulate any kind of hardware automatically.
3. Wide Range of Attack Vectors
There are many potential attack vectors that can be exploited in an embedded system. In C/C++, the most critical are buffer overflows, memory corruption, and code injection.
-
Buffer Overflows: A buffer overflow occurs when more data is written to a buffer than it is designed to hold. This can result in overwriting other data in memory, leading to a crash or allowing an attacker to take control of the system.
-
Memory Corruption: Memory-Corruption occurs when data is written to memory in an unintended way. This can corrupt other data in memory, leading to a crash or allowing an attacker to take control of the system.
-
Code Injection: Injection vulnerabilities happen when malicious code is injected into a program, allowing an attacker to take control of the system or cause it to crash.
You can leverage different dynamic application security tools (DAST) to uncover them. However, solely relying on DAST tools that do black-box testing doesn’t necessarily give you an advantage over attackers, as attackers can also employ similar tools. Moreover, black-box testing tools can’t test your own developed protocols or APIs.
To detect weaknesses earlier than attackers, companies need to leverage their knowledge of internal code design. This is where white-box fuzz testing is invaluable. It’s also the best option for testing if you use your own protocols or create APIs.
Unlike black-box testing, white-box fuzzing analyzes the internal design of a program, knowing the code of the software system, and thus, it is able to identify code issues. Check out the list of vulnerabilities that you can uncover with white-box fuzzing.
4. Shift-left Testing
The earlier you identify a bug, the cheaper it is to fix. However, integrating testing tools into the development process and a CI/CD pipeline can be cumbersome. While static code analysis works directly from your IDE, it lacks developers’ trust due to many false positives and duplicates. Dynamic application security testing tools are rarely adopted by developers and are primarily used by the security team in later stages.
However, there are security testing tools that can be adopted by developers. For example, you can integrate white-box fuzz testing directly into your CI/CD pipeline, enabling your development teams to test their code with every change. All uncovered bugs and vulnerabilities are pinpointed to the exact line of code in the repository and accompanied by inputs that trigger the issue. This allows your engineers to quickly identify the root cause, fix it, and release features faster.
5. Security vs Safety
Embedded systems are often employed in safety-critical applications such as automotive, medical devices, and industrial control systems, where reliability and security are paramount. Testing must address safety-critical requirements and vulnerabilities to ensure system integrity and user safety.
There are two types of tests: positive ("test to pass") and negative ("test to fail"). Positive testing focuses on validating the intended behavior of the software under normal or expected conditions. Negative testing validates how the software handles unexpected or invalid inputs, exceptional conditions, and error scenarios.
While positive testing can be covered with manually written unit tests, negative testing requires an automated approach to cover thousands of test cases. Fuzz testing excels in automating negative testing for both safety and security. It executes a program with thousands of invalid, unexpected, or random inputs, uncovering critical vulnerabilities, edge cases, or even causing application crashes.
6. Manual Regression Testing
Regression testing is often created and maintained manually. As software evolves, the number of test cases grows significantly, making it challenging to maintain and execute comprehensive regression test suites.
Some tools have built-in regression testing. For example, when using Code Intelligence’s fuzz testing, all discovered bugs and vulnerabilities are consistently subjected to regression testing as a standard practice. That means that when an issue is identified, there's no need for additional unit tests. Code Intelligence automatically checks to ensure the same problem won't recur, alerting you if it does.
7. Testing Under Time and Resource Constraints
Effective approaches for embedded software testing at different stages of the development process exist, such as unit tests, regression tests, HW/SW integration, and integration tests. However, time and resource constraints may force teams to omit some of these testing efforts or run them on a reduced scale. Simulating real-world usage requires testing a system under various conditions and with different input types to uncover edge cases.
Automated security testing approaches, like fuzz testing, can help ensure sufficient testing despite time constraints while uncovering edge cases.
Two Main Embedded Software Testing Technologies
Broadly speaking, there are two kinds of embedded testing tools: static and dynamic code analysis. Static analysis is a method of evaluating source code without executing it, while dynamic analysis tests software for bugs and vulnerabilities by executing the code and evaluating its behavior during runtime.
One of the most effective dynamic testing methods for embedded systems is fuzz testing. Fuzz testing involves feeding invalid or random data, known as "fuzzy" data, into the software under test and observing how it behaves, aiming to uncover vulnerabilities or crash the application.
You can learn more about embedded software testing technologies and their limitations by reading the guide "Best Practices for Embedded Software Security Testing."
CI/CD-Integrated Fuzz Testing for Embedded Software
Integrating fuzzing-based testing tools into CI/CD pipelines enables embedded software teams to test their codebase continuously. Such a testing setup alleviates many of the above-mentioned challenges, such as the lack of real-time testing, lack of automation and the safety vs security tradeoff as fuzzing can find both security and safety bugs.
By implementing continuous fuzz testing in the early stages of software development, bugs can be found and fixed before they become an issue. This not only makes embedded systems more secure but also speeds up the development process. If you want to learn more about how CI/CD-integrated fuzz testing can fit into your development process, you can download the white paper "Best Practices for Embedded Software Security Testing."
Inside, you'll discover:
- What fuzz testing is and how it works.
- What black-box and white-box testing is.
- Five reasons to fuzz embedded systems fuzz testing, aka white-box fuzzing.