Editor's Note: This article was originally presented at ESC Boston.
No software engineering process can guarantee secure code, but following the right coding guidelines can dramatically increase the security and reliability of your code. Many embedded systems live in a world where a security breach can be catastrophic.
Embedded systems control much of the world’s critical infrastructure, such as dams, traffic signals, and air traffic control. These systems are increasingly communicating together using COTS networking and in many cases using the Internet itself. Keeping yourself out of the courtroom, if not common decency, demands that all such systems should be developed to be secure.
There are many factors that determine the security of an embedded system. A well-conceived design is crucial to the success of a project. Also, a team needs to pay attention to its development process. There are many different models of how software development ought to be done, and it is prudent to choose one that makes sense. Finally, the choice of operating system can mean the difference between a project that works well in the lab and one that works reliably for years in the real world.
Even the most well thought-out design is vulnerable to flaws when the implementation falls short of the design. This paper focuses on how one can use a set of coding guidelines, called MISRA C and MISRA C++, to help root out bugs introduced during the coding stage.
MISRA C and C++
MISRA stands for Motor Industry Software Reliability Association. It originally published Guidelines For the Use of the C Language In Critical Systems , known informally as MISRA C, in 1998. A second edition of MISRA C was introduced in 2004, and then MISRA C++ was released in 2008. More information on MISRA and the standards themselves can be obtained from the MISRA website .
The purpose of MISRA C and MISRA C++ guidelines are not to promote the use of C or C++ in critical systems. Rather, the guidelines accept that these languages are being used for an increasing number of projects. The guidelines discuss general problems in software engineering and note that C and C++ do not have as much error checking as other languages do. Thus the guidelines hope to make C and C++ safer to use, although they do not endorse MISRA C or MISRA C++ over other languages.
MISRA C is a subset of the C language. In particular, it is based on the ISO/IEC 9899:1990 C standard, which is identical to the ANSI X3.159-1989 standard, often called C ’89. Thus every MISRA C program is a valid C program. The MISRA C subset is defined by 141 rules that constrain the C language. Correspondingly, MISRA C++ is a subset of the ISO/IEC 14882:2003 C++ standard. MISRA C++ is based on 228 rules, many of which are refinements of the MISRA C rules to deal with the additional realities of C++.
For notational convenience, we will use the terms “MISRA”, “MISRA C” or “MISRA C++” loosely in the remainder of the document to refer to either the defining documents or the language subsets.
MISRA is written for safety critical systems, and it is intended to be used within a rigorous software development process. The standard briefly discusses issues of software engineering, such as proper training, coding styles, tool selection, testing methodology, and verification procedures.
MISRA also talks about the ways to ensure compliance with all of the rules. Some of the rules can be verified by a static checking tool or a compiler. Many of the rules are straightforward, but others may not be or may require whole-program analysis to verify. Management needs to determine whether any of the available tools can automatically verify that a given rule is being followed.
If not, this rule must be checked my some kind of manual code review process. Where it is necessary to deviate from the rules, project management must give some form of consent by following a documented deviation procedure. Other non-mandatory “advisory” rules do not need to be followed so strictly, but cannot just be ignored altogether.
The MISRA rules are not meant to define a precise language. In fact, most of the rules are stated informally. Furthermore, it is not always clear if a static checking tool should warn too much or too little when enforcing some of the rules.
The project management must decide how far to go in cases like this. Perhaps a less strict form of checking that warns too little will be used throughout most of the development, until later when a stricter checking tool will be applied. At that point, somebody could manually determine which instances of the diagnostic are potential problems.
Most of the rules have some amount of supporting text that justifies the rules or perhaps gives an example of how the rule could be violated. Many of the rules reference a source, such as parts of the C or C++ standards that state that such behavior is undefined or unspecified.
Before exploring how one could use MISRA, let’s familiarize ourselves with the concepts and some examples of the rules of MISRA.The MISRA rules are classified according to the C or C++ constructs that they restrict.
For example, some of the categories are Environment, Control Flow, Expressions, Declarations, etc. However, I find that most of the rules also fall into a couple of groups according to the errors that they prevent.
The first group of rules consists of those that intend to make the language more portable. For example, the language does not specify the exact size of the built in data types or how conversions between pointer and integer are handled. So, an example of a rule is one that says:
C Rule 6.3/C++ Rule 3-9-2 (advisory) : Typedefs that indicate size and signedness should be used in place of the basic numerical types.
This rule effectively tries to avoid portability problems caused by the implementation defined sizes of the basic types. We will return to this rule in the next section.
Another source of portability problems are undefined behaviors. A program with an undefined behavior might behave logically, or it could abort unexpectedly. For example, using one compiler, a divide by 0 might always return 0.
However, another compiler may generate code that will cause hardware to throw an exception in this case. Many of the MISRA C rules are there to forbid behaviors that produce undefined results because a program that depends on undefined behaviors behaving predictably may not run at all if recompiled with another compiler.
Unlike this first group of rules that guard against portability problems, the second group of rules intends to avoid errors due to programmer confusion. While such rules don’t make the code any more portable, they can make the code a lot easier to understand and much less error prone.
Here’s an example:
C Rule 7.1/C++ Rule 2-13-2 (required) : Octal constants (other than zero) and octal escape sequences (other than “ ”) shall not be used.
By definition, every compiler should do octal constants the same way, but as I will explain later, octal constants almost always cause confusion and are rarely useful. A few other rules are geared toward making code safe for the embedded world. These rules are more controversial, but adherence to them can avoid problems that many programmers would rather sweep under the carpet.
Greg Davis is director of engineering, Compiler Development, at Green Hills Software , Inc. He is a regular contributor contributor to Embedded.com and lecturer at the Embedded Systems Design Conference and DESIGN West. This paper was part of a class on the use of MISRA C and C++ presented by Greg at the Fall Embedded Systems Conference in Boston, Ma. It has also been previously published on the Military and Aerospace DesignLine.
Other C/C++ programming articles by Greg Davis on Embedded.com include: