You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 46 Next »

The C standard, section 3.4.3 [ISO/IEC 9899:2011], defines undefined behavior as

behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements.

Section 4 explains how the standard identifies undefined behavior. (See also undefined behavior 1 of Annex J.)

If a "shall" or "shall not" requirement that appears outside of a constraint is violated, the behavior is undefined. Undefined behavior is otherwise indicated in this International Standard by the words "undefined behavior" or by the omission of any explicit definition of behavior. There is no difference in emphasis among these three; they all describe "behavior that is undefined".

Annex J, section J.2, "Undefined behavior," enumerates the circumstances under which the behavior of a program is undefined. This list is duplicated on the CC. Undefined Behavior page.

Behavior can be classified as undefined by the C standards committee for the following reasons:

  • to give the implementor license not to catch certain program errors that are difficult to diagnose
  • to identify areas of possible conforming language extension: the implementor may augment the language by providing a definition of the officially undefined behavior

Conforming implementations can deal with undefined behavior in a variety of fashions, such as ignoring the situation completely, with unpredictable results; translating or executing the program in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message); or terminating a translation or execution (with the issuance of a diagnostic message). Because compilers are not obligated to generate code for undefined behavior, these behaviors are candidates for optimization. By assuming that undefined behaviors will not occur, compilers can generate code with better performance characteristics.

Unfortunately, undefined behaviors do occur, particularly in the presence of an attacker. Optimizations make it difficult to determine how these systems will behave in the presence of undefined behaviors. This is particularly true when visually inspecting source code that relies on undefined behaviors; a code reviewer cannot be certain if the code will be compiled or if it will be optimized out. Furthermore, just because a compiler currently generates object code for an undefined behavior does not mean that future versions of the compiler are obligated to do the same; the behavior may be viewed as an opportunity for further optimization. Compilers are also not required to issue diagnostics for undefined behavior, so there is frequently no easy way to identify undefined behavior in code.

All of this puts the onus on the programmer to write strictly conforming code, with or without the help of the compiler. Because performance is a primary emphasis of the C language, this situation is likely to get worse before it gets better.

Noncompliant Code Example

An example of undefined behavior in C99 is the behavior on signed integer overflow. (See also rule INT32-C. Ensure that operations on signed integers do not result in overflow.) This noncompliant code example depends on this behavior to catch the overflow.

#include <assert.h>

int foo(int a) {
  assert(a + 100 > a);
  printf("%d %d\n", a + 100, a);
  return a;
}

int main(void) {
  foo(100);
  foo(INT_MAX);
}

This code tests for signed integer overflow by testing to see if a + 100 > a. This test cannot evaluate to false unless an integer overflow occurs. However, because a conforming implementation is not required to generate code for undefined behavior, and signed integer overflow is undefined behavior, this code may be compiled out. For example, GCC version 4.1.1 optimizes out the assertion for all optimization levels, and GCC 4.2.3 optimizes out the assertion for programs compiled with -O2 level optimization and higher.

On some platforms, the integer overflow will cause the program to terminate (before it has an opportunity to test).

Compliant Solution

This compliant solution does not depend on undefined behavior.

#include <assert.h>

int foo(int a) {
  assert(a < (INT_MAX - 100));
  printf("%d %d\n", a + 100, a);
  return a;
}

int main(void) {
  foo(100);
  foo(INT_MAX);
}

Risk Assessment

While it is rare that the entire application can be strictly conforming, the goal should be that almost all the code is allowed for a strictly conforming program (which among other things means that it avoids undefined behavior), with the implementation-dependent parts confined to modules that the programmer knows he needs to adapt to the platform when it changes.

Recommendation

Severity

Likelihood

Remediation Cost

Priority

Level

MSC15-C

high

likely

medium

P18

L1

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Related Guidelines

CERT C++ Secure Coding Standard: MSC15-CPP. Do not depend on undefined behavior

ISO/IEC 9899:1999 Section 3.4.3, "undefined behavior," Section 4, "Conformance," and Annex J.2, "Undefined behavior"

ISO/IEC TR 24772 "BQF Unspecified Behaviour", "EWF Undefined Behaviour" and "FAB Implementation-defined Behaviour"

Bibliography

[Seacord 2005] Chapter 5, "Integers"


  • No labels