Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Expanded a bit and added example.

...

Assertions are primarily intended for use during debugging, and are generally turned off before code is shipped deployed by defining the NDEBUG macro (typically as a flag passed to the compiler). Consequently, assertions should be used to protect against incorrect programmer assumptions and not for runtime error checking.

Assertions should not never be used to check for verify the absence of runtime (as opposed to logic) errors such as:

  • invalid user input (including command line arguments and environment variables)
  • file errors (e.g., errors opening, reading or writing files)
  • network errors (including network protocol errors)file not found
  • out of memory conditions (e.g., malloc() or similar failures)
  • system resource exhaustion (e.g., out of file descriptors, processes, threads)
  • system call errors (e.g., errors executing files, locking or unlocking mutexes)
  • invalid permissions (e.g., file, memory, user)

Code that protects against a buffer overflow, for example, cannot be implemented as an assertion because this code must be presented in the deployed executable.

In particular, assertions are generally unsuitable for server programs or embedded systems in deployment. A failed assertion can lead to a denial-of-service attack if triggered by a malicious user such as if size were in some way derived from client input. In such situations, a soft failure mode such as writing to a log file and rejecting the request is more appropriate.

Code Block
bgColor#ccccff
if (size > SIZE_MAX/sizeof(char *)) {
  fprintf(
    log_file, 
    __FILE__ ": size %zu exceeds SIZE_MAX/sizeof(char *)\n", 
    size
  );
  size = SIZE_MAX/sizeof(char *);
}
table_size = size * sizeof(char *);

Anchor
ncce_malloc
ncce_malloc

Noncompliant Code Example (malloc())

The noncompliant code example below uses the assert() macro to verify that memory allocation succeeded. Since memory availability depends on the overall state of the system and may become exhausted at any point during a process lifetime, a robust program must be prepared to gracefully handle and recover from its exhaustion. Thus, using the assert() macro to verify that a memory allocation succeeded would be inappropriate as doing so might lead to an abrupt termination of the process and open up the possibility of a denial-of-service attack. See also MEM11-C. Do not assume infinite heap space and MEM32-C. Detect and handle memory allocation errors.

Code Block
bgColor#ffcccc

char* dupstring(const char *str) {
    size_t len;
    char *dup;

    len = strlen(str);
    dup = (char*)malloc(len + 1);
    assert(NULL != dup);

    memcpy(dup, str, len + 1);
    return dup;
}

Anchor
cs_malloc
cs_malloc

Compliant Solution (malloc())

The compliant solution below demonstrates how to detect and handle possible memory exhaustion.

Code Block
bgColor#ccccff

char* dupstring(const char *str) {
    size_t len;
    char *dup;

    len = strlen(str);
    dup = (char*)malloc(len + 1);
     /* detect and handle memory allocation error */
    if (NULL == dup)
      return NULL;

    memcpy(dup, str, len + 1);
    return dup;
}

Risk Assessment

Assertions are a valuable diagnostic tool for finding and eliminating software defects that may result in vulnerabilities. The absence of assertions, however, does not mean that code is incorrect.

...