We have seen in previous articles how important it is for cybersecurity to be integrated early in the design (Threat Model) and development processes (Common Vulnerabilities and Exposure + Static Application Security Testing).
Cybersecurity must be part of the entire development cycle in order to write a secure software, even if the system design seems already totally secure “on paper”. Otherwise, all development efforts will be worthless.
There are several ways to introduce vulnerabilities into software and it is the developer’s responsibility to think about security when writing code, and Software Project Manager’s responsibility to train the team to be aware of and apply secure coding principles in order to deliver safe and reliable software.
In this fourth and final article on cybersecurity in medical devices, we will review several concepts based on Debiotech’s experience that can help develop a more secure medical device.
1. Rely on the Security Features Provided by the Hardware
Modern microcontrollers generally provide several advanced security features that the software project should leverage to improve security. One of the first thing to consider must be writing software that can take advantage of all these features. The most important is probably secure boot, which ensure that only trusted software is executed. However, hardware isolation mechanisms may also be needed to isolate sensitive code or data (e.g., ARM Trustzone) and to write drivers that can exploit hardware cryptographic acceleration (even mainstream microcontrollers offers such features, like cortex-m23 based).
Figure 1: A typical Secure Boot chain of trust for Linux System
2. Rely on the Security Features Provided by the Tools
Applications should be hardened using the security options provided by the build environment. If a C or C++ toolchain is used, options that will protect against vulnerabilities should be enabled: enable all warnings (-Wall) and treat them as errors (-Werror) to force the development team to detect and fix potential problems such as functions misuse. The software should also be compiled as position-independent (-fPIC, -fPIE) to protect against certain Return-oriented programming (ROP) attacks, and based on the needs, enable options like -fstack-protector, to protect against stack overflow.
3. Reduce The Attack Surface As Much As Possible.
(RT)OSes (Real-Time Operating Systems), SOUP (Software of Unknown Pedigree) and the code must use only strictly necessary features. The more protocols, drivers or libraries are used, the higher the risk of introducing a vulnerability increases. A quick look at the CVE database searching for “linux kernel”, and a large number of CVEs related to very specific drivers not needed on the device will be found.
Figure 2: Linux kernel configuration is highly versatile. Disable all features that are not needed
4. Never Trust User Input
Every data external to the application must be checked before being processed. This includes, but is not limited to, data files stored on disk, user input and data coming from external communication channels (Wi-Fi, Bluetooth, …). For the developers, it means that special care must be taken while handling this data:
- Input files must be authenticated using cryptographic signatures
- User input must be checked. Some basic principles must be followed:
– Check data length before copying it into buffers to prevent buffer/stack overflows.
– Use regex if a specific pattern for input is expected.
– Use programming language facilities to prepare Structure Query Language (SQL) queries if the input is meant to be used in a database to prevent SQL injection.
- Always authenticate the peer before exchanging data. Secure protocols must always be used (e.g., Transport Layer Security (TLS) 1.2 at least for network communications)
- All the code impacted by external input must be identified and tested. Fuzzing tools like American Fuzzy Lop (AFL) may help in this process.
Figure 3: AFL is a very popular fuzzer which tests program robustness against any inputs
5. Pay Special Attention to Memory Management
Memory management is critical in many applications. Programs that allow dynamic memory allocation without protection may be dangerous as they can lead to errors such as information disclosure or arbitrary code execution if not handled properly. Developers must be aware of the problems related to memory leak, double free, or dangling pointer dereference. Special care must be taken when using dynamic memory. Prefer usage of smart pointers and test the code to detect memory-related errors (using an instrumentation framework like Valgrind may help).
Figure 4: Valgrind is a powerful tool that can detect memory errors. Popular integrated development environments (IDE) like QtCreator offer nice integration.
6. Identify and Protect Sensitive Data
Medical devices and a majority of embedded systems store their data in embedded MMC (eMMC), or other type of flash memories, in an unencrypted form. Keep in mind that even if the memory is soldered to the device, it is easy to read/write/modify its content.
Figure 5: It is easy to dump an eMMC memory content by soldering a couple of wires (image from sparkfun SD Sniffer)
It is often not feasible to encrypt the entire disk for performance reason, but all personal health information (PHI) and secrets (e.g., tokens used to access web APIs) must be encrypted. The cryptographic keys must be stored securely in a dedicated area of the microcontroller if it provides anti-tamper protection, or in a secure element (SE).
Figure 6: A SE highly improves the device security by providing safe storage, secure key generation and more (image : ATECC508A from Microchip)
7. Use Only Strong Algorithms, and Never Write Own Cryptography
Cryptography is a highly specialized field. All algorithms used in computer cryptography were developed for years. Software developers usually do not master algorithms details (and they do not have to!), but they must be aware of the way to use them efficiently. Here are a few hints:
- Writing a new implementation is complex and error prone. Inserting a vulnerability in a critical algorithm can be disastrous (see heartbleed bug). Instead, dedicated libraries must be used. Very well-tested ones exist for all languages. In embedded systems, openssl for linux based devices or mbedTLS for bare-metal devices are good choices that will provide all needed features.
- Using a bug-free library does not mean that bug-free cryptography will be written. The documentation must be followed to use the functions as they are intended to and the algorithm correctly used (e.g., a common mistake is to use a fixed initialization vector (IV) for advanced encryption standard (AES) encryption).
- Algorithms may become obsolete because a flaw was discovered, or it is considered too weak due to increase of computational power. Only algorithms that are considered safe for the next coming years must be used. The FIPS 140-2 Algorithm Lists can be checked to know which algorithms are approved.
Debiotech, a Support for Cybersecurity in Medical Device Software
Writing secure software is a hard task that requires attention of the entire development team, from the requirements specification to testing phase. There are several rules to follow with the most important being to keep pace and always train the team about latest security best practices.
We hope that these few articles have allowed you to deepen your knowledge on cybersecurity, why it has become essential and how it can be applied in the development of medical devices (for embedded software or software as medical device).
Debiotech has developed over the years a strong expertise in cybersecurity and can accompany you through the steps of your development to design and implement a secure and robust medical device.
If you have any questions and/or needs on the subject, you can contact our team who will be happy to answer your request.