At Secure Code Warrior, we are always looking to expand our training coverage. To empower embedded developers and security managers to build secure embedded systems, we are tapping into the world of embedded systems security. In this post, we will share the key things developers and architects should know about building secure embedded systems.
An embedded device is an independent computing unit, with its own microprocessor-based hardware and software. They are typically used to perform dedicated functions, either independently, or as parts of larger systems.
What is an embedded device and system?
Embedded devices are purpose-built computing systems with minimalistic hardware and software, programmed to perform specific functions. Depending on their purpose, the complexity of embedded devices can vary.
If an embedded system has to perform complicated, resource-intensive operations, its hardware may comprise multiple processors, with various interconnected peripherals. If, however, it’s designed to do a simple task, it may just contain a single microcontroller.
Some embedded systems are black-boxes with no user interface, while others can have detailed, graphical user interfaces.
Examples of embedded devices
Embedded devices find applications across multiple industries; from aerospace to computing, and from home appliances to healthcare. Here are a few examples:
- A thermostat within an air conditioner.
- A fitness tracker, with small electrical components running a tiny operating system, that can do nothing more than record and sync your health stats.
- Digital climate control systems.
- Navigation systems within an airplane.
- Digital watches.
- GPS devices.
- Wi-Fi components within microwave ovens and other consumer electronics.
- Blood pressure and heartbeat monitors.
- Networking component within an MRI machine that sends data to the cloud.
Is a laptop an embedded device?
Short answer: No. Long answer:
By definition, an embedded device encapsulates all the software and hardware it needs to perform its functions. A laptop indeed wraps up various hardware components in a metal body, and encompasses all the software it needs to deliver its feature set.
However, unlike typical embedded systems, which have a pre-defined, minimal feature set, a laptop is more general purpose and can be used to do a wide array of activities.
Embedded devices in Internet of Things
Embedded systems are at the heart of IoT. Your smart fridge can be controlled via your mobile device, because of an embedded device. It’s an embedded device in your security system that lets you view CCTV footage from a thousand miles away. And when you push a button to put your car on auto-pilot, behind-the-scenes, it’s an embedded device that does all the magic.
Embedded software development life cycle
The following steps are involved in the typical software development life cycle of an embedded device:
Step 1: Purpose and requirements
An embedded device has a distinct and precise feature set. This is why it’s very important to explicitly state its purpose and requirements before starting the development. Creating a detailed design document for the system is recommended. Answer questions like:
- What functions will the device perform?
- Will it be a part of another machine, or will it function on its own?
- Should the device have a user interface? If yes, does a command line suffice, or is a graphical UI needed?
- What size, cost, or power consumption limits must the device adhere to?
- Are there any performance benchmarks? E.g. is the device expected to respond in real-time, or within certain thresholds?
Step 2: System architecture
Once we have identified the unique system requirements, we are ready to design the system architecture. Answer questions like:
- What hardware components will be required to develop the device? This includes identifying the circuits, processor chips and micro-controllers, along with any required internal and external peripherals.
- How much power will the components require?
- Will the device be connected to the internet?
- Which interfaces must be added/developed to allow the device to connect to other devices, or the larger machine?
- How will you implement encryption? (think algorithms, key storage etc.)
- How will you identify and avoid any potential vulnerabilities, exploits, and malware?
Step 3: Select the operating system
The choice of operating system often dictates the efficacy of your embedded applications, so choose wisely. Windows for IoT may score high in graphics, but lacks hardware support. Embedded Linux and Android are free, whereas VxWorks and Windows for IoT have licensing costs.
With open-source systems like Linux and Android, your developers have more control over the kernel; however, with proprietary systems, the default feature set of the kernel is all that you can work with.
It’s also important to consider the security implications while choosing an operating system. Do security patches get released quickly, in case any vulnerabilities are discovered? Is there any built-in protection against the most common cybersecurity attacks?
Step 4: The development tools
Different programming languages and frameworks have various pros and cons. Depending on your feature, speed, and reliability requirements, you may choose one language/framework over the other.
If your application is web-based, and you want high performance, go with Java.
However, if you want the fastest throughput, go with C/C++. If you want the best third-party library support, go with Python.
Step 5: Code, refactor, test, code some more
Once you have decided on the development platform, you are ready to start coding. Remember that embedded systems are sensitive devices with limited hardware and software resources. It’s thus of pivotal importance to keep in mind the best practices regarding application security and performance.
Code reviews help in optimizing code and identifying any potential bugs. You also need to test as rigorously as possible. Ensure that you compile an exhaustive list of all the test-cases that the device may encounter when used in production.
Step 6: Maintenance and support
Just like any other application/device, the life cycle doesn’t end when the embedded device has been shipped and installed. As the device gets used in production, new use-cases are identified, which require new features to be added. Any reported bugs may also require new firmware updates to be released.
Top embedded programming languages
There are many reputable organizations, like IEEE and TIOBE, that rank programming languages by popularity and use. However, regardless of whether you check the TIOBE index, or the IEEE Spectrum, you will notice a common theme.
The top 3 languages are always C, Python, and Java. The TIOBE index for July 2021 ranks C at #1, Java at #2, and Python at #3. According to IEEE Spectrum’s ranking for embedded programming languages, the order is: Python, Java, and then C.
How does firmware written in Java, C, or Python differ?
Different languages manage memory, interactions with the OS, and runtime, in different ways. E.g. Java applications execute in a specialized runtime environment known as Java Virtual Machine, or JVM.
Memory management within C/C++ is manual, however, in Java or Python, you don’t have to worry about managing memory yourself; the language does it for you. What’s more is that in C, you have to collect your garbage yourself; i.e. if you dynamically allocate a piece of memory, you have to explicitly free it yourself. If you don’t, it will lead to a memory leak. In Java or Python, garbage collection is done automatically. However, C is intrinsically much faster than Java or Python.
One thing that C and Java (but especially C) lack in comparison to Python is the availability of third-party libraries. Python has a richer database of libraries, which makes it much easier for developers to add certain features.
Overall, choose C if:
- You want a low-level interface with the operating system and hardware.
- You want the best possible performance.
- Your developers are skilled at C/C++.
- Don’t need too much external/third-party support.
Go with Python if:
- You want to implement data science or machine learning algorithms.
- You want rich, third-party library support.
- You are okay with trading-off speed for ease-of-use.
- You have limited experience in embedded development.
- You don’t need a low-level interface with the OS or hardware.
Go with Java if:
- You are building web applications.
- You want more ease-of-development than C, but still want more performance than Python.
- Don’t need too much external/third-party support.
Will C slowly stop being a top choice for embedded development?
High-level languages make it easier to code by abstracting away the complexities that simply can’t be avoided in C. However, for decades, despite the release of fancier languages and frameworks, C has remained a top choice in embedded development.
This is because the speed, performance, and reliability it offers, are still unparalleled. The fact that Python and large parts of the Java ecosystem (including the runtime, language, and compiler) are written in C, speaks volumes about the language’s efficiency and durability.
With that said, C is undeniably harder to learn for new developers, who prefer the simplicity of languages like Python. This is creating a noticeable decline in the size of the C developer community. The rise in development of AI and ML applications is also driving people to choose Python over C, simply because it offers greater library support.
It’s important to remember that Python has only recently become part of the top embedded programming language debate. Yes, its adoption may continue to rise in the years to come, but we don’t expect C to ever stop being a top choice for embedded development.
Why secure coding is important in C/C++
A lot can go wrong while writing code in C/C++. Accessing a memory address that no longer points to valid data, or incorrectly sharing data between threads, can cause your entire application to crash.
For embedded devices, these implications are far more significant, as a crash inside them can cause a much larger machine to stop functioning. E.g. if the auto-pilot module in a car shuts down, the car will no longer be able to avoid obstacles.
Here are more reasons why secure coding of C/C++ applications is so important:
- Insecure memory handling (null pointers, memory corruption, stack overflows, buffer and heap overflows) cause the entire application to crash.
- Pointers are part and parcel of C and C++. Handling them requires utmost care, and in-depth knowledge of underlying concepts.
- Garbage collection is manual. This makes it compulsory for programmers to explicitly delete any dynamically allocated memory. Unfree or incorrectly freed memory can lead to memory leaks or corruption.
- There is a lack of standardized APIs for advanced data structures (e.g. hash tables, sets etc.), which forces developers to reinvent the wheel.
It’s absolutely critical that developers are trained in secure coding for C/C++ applications. Secure Code Warrior provides a personalized learning platform with interactive challenges, courses, and assessments that can enable developers to write secure C/C++ code. Here, we are talking about language:framework-specific content, not just a tweak on general conceptual training.
Check out how we help automotive and transportation industries. Or try an embed-focus coding challenge today to experience how we empower developers to write secure embedded applications!