There are two types of IoT devices: high-end devices and low-end devices. The operating systems that are used for high-end devices include fully functional ones like Windows, Linux or Android. For example, Amazon’s echo device uses Fire OS based on Android whilst Samsung smartwatches use Tizen based on Linux. These devices are wired for power or have batteries that are regularly charged. However, low-end devices have a very small amount of memory and work with a low amount of energy often with fixed lifespans without the possibility of being recharged. These devices use operating systems such as Contiki or even home-grown ones, designed specifically for these types of low memory/ low energy constraints. They use low power consumption and open source ISA processors such as RISC-V.
With the rapid growth and proliferation of low-end IoT devices, and the countless number of companies creating them, many developers are faced with the challenges of writing small applications that can still be fit for purpose. Choosing the correct programming language for writing embedded software is crucial for delivering high-performance and efficient devices.
“If you’re writing for a sensor on bare metal, you’re probably using C!”
Ian Skerrett, Eclipse Foundation.
At the device level, computing power is usually quite limited. The C/ C++ programming languages work best here because they are ideal for writing code close to the hardware layer, they don’t require a lot of processing power, and are able to work directly with the RAM. This makes it a no-brainer for sensor and gateway hardware layer applications. However, C/ C++ are infamous for their syntax becoming cluttered and messy quickly if developers are not well-versed in best practices.
C has been around since the early seventies and C++ since the early eighties. Both are still actively used today, and for good reason. They were created to be compiled languages capable of providing low-level access to memory that can be efficiently mapped to machine instructions. This, alongside minimal runtime support for faster performance. However, one criticism is the perceived complexity. It’s because of this that many developers have a steeper learning curve than they might for other, newer languages.
“Since C and C++ are not memory-safe languages, attacks can exploit memory vulnerabilities in C/C++ code to gain illegal access.”
Yuning Liang, Xcalibyte
When coding in C, poorly written code may cause strange bugs that take weeks or months to find, and that exhibit transient and misleading behavior. They may foul the stack or heap and cause eventual failure or create vulnerabilities, hundreds of instructions after the precipitating event. These are often due to the challenges of manual memory management.
Here are a few common issues found when having to manually manage memory in code.
|Missing Free (MSF)||The program has allocated heap memory but failed to free that piece of memory. Data on the heap must be allocated and de-allocated manually, using malloc and free. You, the programmer, are in charge of freeing heap memory that is no longer needed.
Not freeing memory can result in memory leaks. This becomes a problem if the process is a long running one. It can result in core dumps, unexpected behavior and affect system performance. If an attacker can cause a memory leak, they can take advantage of issues that occur in low memory conditions or launch DoS attacks.
Related vulnerability - CWE-401: Missing Release of Memory after Effective Lifetime
|Null Pointer Dereference (NPD)||It’s always necessary to check the pointer to the memory returned by calloc/malloc. If this pointer turns out to be null, the memory allocation should be considered unsuccessful, and no operations should be performed using that pointer. It could cause a segmentation fault or unpredictable program behavior. NPDs often result from rarely encountered error conditions which means they are hard to find in normal testing phases.
The implications of this are that the integrity of the application may be compromised, confidential data may be exposed, and system availability may be interrupted.
Related vulnerability - CWE-476: NULL Pointer Dereference
|Use Dangling Reference (UDR)||A dangling pointer is a pointer that has been used to refer to an invalid memory resource such as deallocated memory. This can lead to memory faults with undefined behavior. For example, accessing the memory may cause the application to crash.
These issues are frequently ignored as the old view was that they do not cause dangerous security risks. However, it has been proven that they can allow attackers to cause remote-code execution attacks. This can be achieved by altering where the pointer points to such as redirecting it to malicious code or by overwriting the memory being pointed at with malicious code.
Related vulnerability - CWE-825: Expired Pointer Dereference
|Use After Free (UAF)||If the program attempts to access memory that has been freed, it can cause the program to crash or cause unexpected program behavior. Use After Free conditions are directly related to dangling pointers.
This condition may affect integrity by corrupting valid data, cause program crashes through corrupt data and allow an attacker to launch malicious code.
Related vulnerability - CWE-416: Use After Free
There are many more problems than the ones listed above and the industry standard way of identifying them is to utilize static code analysis tools before compiling the program. Xcalscan, the static code analysis tool created by Xcalibyte, does address these memory issues and more.
Many of Xcalibyte’s customers create IoT products and are very aware of the need to retain competitive advantage in getting their product to market early without quality and security being sacrificed. Using Xcalscan helps them identify those hard-to-find bugs during ‘static-time’ instead of runtime, significantly reducing the remediation time as they are harder to fix. By exploring further down the compiler process, we can investigate data flows and expose where memory is being mis-managed. This is achieved by using flow sensitive analysis, interprocedural analysis, context sensitive analysis and object sensitive analysis.
Developers with the skills and wherewithal to truly understand the complexity of programming with C/C++ languages are harder to find as more and more of them focus on learning interpreted languages over compiled ones. Few developers are comfortable writing in languages closer to machine code without the added benefits of features for automatic memory management such as garbage collection. However, the need for C/C++ is still as important as when they were first created, and some might even say these languages power the world. Let’s not forget that mainstream operating systems such as Windows, Linux and MacOS are mostly written in C. In daily life, C/C++ sit behind activities such as your alarm clock waking you up in the morning, the dashboard display giving you information in your car to the electronic payment you make for buying your first cup of coffee before work.
For both novice and experienced developers, the use of static code analysis tools helps bring peace of mind during the various phases of software development. They assist in ensuring your memory management is of high quality thereby delivering integrity, performance and security in your software.