Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: updated for consistency with TS 17961

Many library functions accept a string or wide string argument with the constraint that the string they receive is properly null-terminated. Passing a character sequence or wide character sequence that is not null-terminated to such a function can result in accessing memory that is outside the bounds of the object.  Do not pass a character sequence or wide character sequence that is not null-terminated to a library function that expects a string or wide string argument. 

Noncompliant Code Example

This code example is noncompliant because the character sequence str will not be null-terminated when passed as an argument to printf().  See STR11-C. Do not specify the bound of a character array initialized with a string literal.

Code Block
bgColor#FFcccc
langc
char str[3] = "abc";
printf("%s\n", str);

Compliant Solution

This compliant solution does not specify the bound of a character array in the array declaration. If the array bound is omitted, the compiler allocates sufficient storage to store the entire string literal, including the terminating null character.

Code Block
bgColor#ccccff
langc
char str[3] = "abc";
printf("%s\n", str);

Noncompliant Code Example

This code example is noncompliant because the wide character sequence cur_msg will not be null-terminated when passed to wcslen(). This will occur if lessen_memory_usage() is invoked while cur_msg_size still has its initial value of 1024.

 

Code Block
bgColor#ffcccc
langc
wchar_t *cur_msg = NULL;
size_t cur_msg_size = 1024;
size_t cur_msg_len = 0;

void lessen_memory_usage(void) {
  wchar_t *temp;
  size_t temp_size;

  /* ... */

  if (cur_msg != NULL) {
    temp_size = cur_msg_size / 2 + 1;
    temp = realloc(cur_msg, temp_size * sizeof(wchar_t));
    // temp & cur_msg might not be null-terminated
    if (temp == NULL) {
      /* Handle error */
    }

    cur_msg = temp;
    cur_msg_size = temp_size;
    cur_msg_len = wcslen(cur_msg); // error 
  }
}

Compliant Solution

In this compliant solution, cur_msg will always be null-terminated when passed to wcslen().

Code Block
bgColor#ccccff
langc
wchar_t *cur_msg = NULL;
size_t cur_msg_size = 1024;
size_t cur_msg_len = 0;

void lessen_memory_usage(void) {
  wchar_t *temp;
  size_t temp_size;

  /* ... */

  if (cur_msg != NULL) {
    temp_size = cur_msg_size / 2 + 1;
    temp = realloc(cur_msg, temp_size * sizeof(wchar_t));
    // temp & cur_msg might not be null-terminated
    if (temp == NULL) {
      /* Handle error */
    }

    cur_msg = temp;
    // cur_msg now properly null-terminated
    cur_msg[temp_size - 1] = L'\0'; 
    cur_msg_size = temp_size;
    cur_msg_len = wcslen(cur_msg); 
  }
}

Character arrays must be null-terminated before they may be safely passed as arguments to standard string-handling functions, such as strcpy() or strlen(). These functions, as well as other string-handling functions defined by the C Standard, depend on the existence of the string's null-termination character to determine the length of the string. Likewise, character sequences must be null-terminated before iterating on the sequence where the termination condition of the loop depends on the existence of a null-termination character within the memory allocated for the sequence, as in the following example:

Code Block
langc
void func(void) {
  char ntbs[16];

  for (size_t i = 0; i < sizeof(ntbs); ++i) {
    if (ntbs[i] == '\0') {
      break;
    }
  }

...

Noncompliant Code Example (strncpy())

...

Code Block
bgColor#FFcccc
langc
#include <string.h>
 
enum { NTBSSTR_SIZE = 32 };
 
size_t func(const char *source) {
  char ntbsstr[NTBSSTR_SIZE];

  ntbsstr[sizeof(ntbsstr) - 1] = '\0';
  strncpy(ntbsstr, source, sizeof(ntbsstr));
  return strlen(ntbsstr);
}

Compliant Solution (Truncation)

...

Code Block
bgColor#ccccff
langc
#include <string.h>
 
enum { NTBSSTR_SIZE = 32 };
 
size_t func(const char *source) {
  char ntbsstr[NTBSSTR_SIZE];

  strncpy(ntbsstr, source, sizeof(ntbsstr) - 1);
  ntbsstr[sizeof(ntbsstr) - 1] = '\0';
  return strlen( ntbsstr);
}

Compliant Solution (Copy without Truncation)

...

Code Block
bgColor#ccccff
langc
#include <string.h>
 
enum { NTBSSTR_SIZE = 32 };
 
size_t func(const char *source) {
  char ntbsstr[NTBSSTR_SIZE];

  if (source) {
    if (strlen(source) < sizeof(ntbsstr)) {
      strcpy(ntbsstr, source);
    } else {
      /* Handle string-too-large */
    }
  } else {
    /* Handle null pointer */
  }
  return strlen(ntbsstr);
}

Compliant Solution (strncpy_s(), C11 Annex K)

...

Code Block
bgColor#ccccff
langc
#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>
  
enum { NTBSSTR_SIZE = 32 };
  
size_t func(const char *source) {
  char a[NTBSSTR_SIZE];

  if (source) {
    errno_t err = strncpy_s(a, sizeof(a), source, 5);
    if (err != 0) {
      /* Handle error */
    }
  } else {
    /* Handle null pointer */
  }
  return strlen_s(s, sizeof(a));
}

Noncompliant Code Example (realloc())

One method to decrease memory usage in critical situations when all available memory has been exhausted is to use the realloc() function to halve the size of message strings. The standard realloc() function has no concept of strings. As a result, if realloc() is called to decrease the memory allocated for a string, the null-termination character may be truncated.

This noncompliant code example fails to ensure that cur_msg is properly null-terminated:

Code Block
bgColor#ffcccc
langc
#include <stdlib.h>
 
char *cur_msg = NULL;
size_t cur_msg_size = 1024;
 
void lessen_memory_usage(void) {
  char *temp;
  size_t temp_size;

  if (cur_msg != NULL) {
    temp_size = cur_msg_size / 2 + 1;
    temp = realloc(cur_msg, temp_size);
    if (temp == NULL) {
      /* Handle error */
    }
    cur_msg = temp;
    cur_msg_size = temp_size;
    fputs(stderr, cur_msg);
  }
}

Because realloc() does not guarantee that the character sequence is properly null-terminated, and the function subsequently passes cur_msg to a library function (fputs()) that expects null-termination, the result is undefined behavior.

Compliant Solution (realloc())

In this compliant solution, the lessen_memory_usage() function ensures that the resulting string is always properly null-terminated:

Code Block
bgColor#ccccff
langc
#include <stdlib.h>
 
char *cur_msg = NULL;
size_t cur_msg_size = 1024;

void lessen_memory_usage(void) {
  char *temp;
  size_t temp_size;

  if (cur_msg != NULL) {
    temp_size = cur_msg_size / 2 + 1;
    temp = realloc(cur_msg, temp_size);
    if (temp == NULL) {
      /* Handle error */
    }
    cur_msg = temp;
    cur_msg_size = temp_size;

    /* Ensure string is null-terminated */
    cur_msg[cur_msg_size - 1] = '\0';
  }
  fputs(stderr, cur_msg);
}

Risk Assessment

Failure to properly null-terminate a character sequence that is passed to a library function that expects a string can result in buffer overflows and the execution of arbitrary code with the permissions of the vulnerable process. Null-termination errors can also result in unintended information disclosure.

...