Main Content

CWE Rule 170

Improper Null Termination

Since R2023a

Description

Rule Description

The software does not terminate or incorrectly terminates a string or array with a null character or equivalent terminator.

Polyspace Implementation

The rule checker checks for these issues:

  • Invalid use of standard library string routine

  • Missing null in string array

  • Misuse of readlink()

  • Tainted NULL or non-null-terminated string

Examples

expand all

Issue

This issue occurs when a string library function is called with invalid arguments.

Risk

The risk depends on the type of invalid arguments. For instance, using the strcpy function with a source argument larger than the destination argument can result in buffer overflows.

Fix

The fix depends on the standard library function involved in the defect. In some cases, you can constrain the function arguments before the function call. For instance, if the strcpy function:

char * strcpy(char * destination, const char* source)
tries to copy too many bytes into the destination argument compared to the available buffer, constrain the source argument before the call to strcpy. In some cases, you can use an alternative function to avoid the error. For instance, instead of strcpy, you can use strncpy to control the number of bytes copied.

See examples of fixes below.

If you do not want to fix the issue, add comments to your result or code to avoid another review. See:

Example — Invalid Use of Standard Library String Routine Error
 #include <string.h>
 #include <stdio.h>
 
 char* Copy_String(void)
 {
  char *res;
  char gbuffer[5],text[20]="ABCDEFGHIJKL";

  res=strcpy(gbuffer,text);  //Noncompliant
  /* Error: Size of text is less than gbuffer */

  return(res);
 }

The string text is larger in size than gbuffer. Therefore, the function strcpy cannot copy text into gbuffer.

Correction — Use Valid Arguments

One possible correction is to declare the destination string gbuffer with equal or larger size than the source string text.

#include <string.h>
 #include <stdio.h>
 
 char* Copy_String(void)
 {
  char *res;
  /*Fix: gbuffer has equal or larger size than text */
  char gbuffer[20],text[20]="ABCDEFGHIJKL";

  res=strcpy(gbuffer,text);

  return(res);
 }
Issue

This issue occurs when a string does not have enough space to terminate with a null character '\0'.

This defect applies only for projects in C.

Risk

A buffer overflow can occur if you copy a string to an array without assuming the implicit null terminator.

Fix

If you initialize a character array with a literal, avoid specifying the array bounds.

char three[]  = "THREE";
The compiler automatically allocates space for a null terminator. In the preceding example, the compiler allocates sufficient space for five characters and a null terminator.

If the issue occurs after initialization, you might have to increase the size of the array by one to account for the null terminator.

In certain circumstances, you might want to initialize the character array with a sequence of characters instead of a string. In this situation, add comments to your result or code to avoid another review. See:

Example — Array size is too small
void countdown(int i)
{
    static char one[5]   = "ONE";
    static char two[5]   = "TWO";
    static char three[5] = "THREE"; //Noncompliant
}

The character array three has a size of 5 and 5 characters 'T', 'H', 'R', 'E', and 'E'. There is no room for the null character at the end because three is only five bytes large.

Correction — Increase Array Size

One possible correction is to change the array size to allow for the five characters plus a null character.

void countdown(int i)
{
    static char one[5]   = "ONE";
    static char two[5]   = "TWO";
    static char three[6] = "THREE";
}
Correction — Change Initialization Method

One possible correction is to initialize the string by leaving the array size blank. This initialization method allocates enough memory for the five characters and a terminating-null character.

void countdown(int i)
{
    static char one[5]   = "ONE";
    static char two[5]   = "TWO";
    static char three[]  = "THREE";
}
Issue

This issue occurs when you pass a buffer size argument to readlink() that does not leave space for a null terminator in the buffer.

For instance:

ssize_t len = readlink("/usr/bin/perl", buf, sizeof(buf));
The third argument is exactly equal to the size of the second argument. For large enough symbolic links, this use of readlink() does not leave space to enter a null terminator.

Risk

The readlink() function copies the content of a symbolic link (first argument) to a buffer (second argument). However, the function does not append a null terminator to the copied content. After using readlink(), you must explicitly add a null terminator to the buffer.

If you fill the entire buffer when using readlink, you do not leave space for this null terminator.

Fix

When using the readlink() function, make sure that the third argument is one less than the buffer size.

Then, append a null terminator to the buffer. To determine where to add the null terminator, check the return value of readlink(). If the return value is -1, an error has occurred. Otherwise, the return value is the number of characters (bytes) copied.

Example — Incorrect Size Argument of readlink
#include <unistd.h>

#define SIZE1024 1024

extern void display_path(const char *);

void func() {
    char buf[SIZE1024];
    ssize_t len = readlink("/usr/bin/perl", buf, sizeof(buf)); //Noncompliant
    if (len > 0) {
        buf[len - 1] = '\0';
    }
    display_path(buf);
}

In this example, the third argument of readlink is exactly the size of the buffer (second argument). If the first argument is long enough, this use of readlink does not leave space for the null terminator.

Also, if no characters are copied, the return value of readlink is 0. The following statement leads to a buffer underflow when len is 0.

buf[len - 1] = '\0';

Correction — Make Sure Size Argument is One Less Than Buffer Size

One possible correction is to make sure that the third argument of readlink is one less than size of the second argument.

The following corrected code also accounts for readlink returning 0.

#include <stdlib.h>
#include <unistd.h>

#define fatal_error() abort()
#define SIZE1024 1024

extern void display_path(const char *);

void func() {
    char buf[SIZE1024];
    ssize_t len = readlink("/usr/bin/perl", buf, sizeof(buf) - 1); 
    if (len != -1) {
        buf[len] = '\0';
        display_path(buf);
    }
    else {
        /* Handle error */
        fatal_error();
    }
}
Issue

This issue occurs when strings from unsecure sources are used in string manipulation routines that implicitly dereference the string buffer, for instance, strcpy or sprintf.

Tainted NULL or non-null-terminated string raises no defect for a string returned from a call to scanf-family variadic functions. Similarly, no defect is raised when you pass the string with a %s specifier to printf-family variadic functions.

Risk

If a string is from an unsecure source, it is possible that an attacker manipulated the string or pointed the string pointer to a different memory location.

If the string is NULL, the string routine cannot dereference the string, causing the program to crash. If the string is not null-terminated, the string routine might not know when the string ends. This error can cause you to write out of bounds, causing a buffer overflow.

Fix

Validate the string before you use it. Check that:

  • The string is not NULL.

  • The string is null-terminated

  • The size of the string matches the expected size.

Extend Checker

By default, Polyspace® assumes that data from external sources are tainted. See Sources of Tainting in a Polyspace Analysis. To consider any data that does not originate in the current scope of Polyspace analysis as tainted, use the command line option -consider-analysis-perimeter-as-trust-boundary.

Example — Getting String from Input
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define SIZE128 128
#define MAX 40
extern void print_str(const char*);
void warningMsg(void)
{
	char userstr[MAX];
	read(0,userstr,MAX);
	char str[SIZE128] = "Warning: ";
	strncat(str, userstr, SIZE128-(strlen(str)+1));//Noncompliant //Noncompliant
	print_str(str);
}


In this example, the string str is concatenated with the argument userstr. The value of userstr is unknown. If the size of userstr is greater than the space available, the concatenation overflows.

Correction — Validate the Data

One possible correction is to check the size of userstr and make sure that the string is null-terminated before using it in strncat. This example uses a helper function, sansitize_str, to validate the string. The defects are concentrated in this function.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define SIZE128 128
#define MAX 40
extern void print_str(const char*);
int sanitize_str(char* s) {
	int res = 0; 
	if (s && (strlen(s) > 0)) { // Noncompliant-TAINTED_STRING only flagged here //Noncompliant
		// - string is not null
		// - string has a positive and limited size
		// - TAINTED_STRING on strlen used as a firewall
		res = 1;
	}
	return res; 
}
void warningMsg(void)
{
	char userstr[MAX];
	read(0,userstr,MAX);
	char str[SIZE128] = "Warning: ";
	if (sanitize_str(userstr))	
		strncat(str, userstr, SIZE128-(strlen(str)+1));
	print_str(str);
}
Correction — Validate the Data

Another possible correction is to call function errorMsg and warningMsg with specific strings.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define SIZE128 128

extern void print_str(const char*);

void warningMsg(char* userstr)
{
    char str[SIZE128] = "Warning: ";
    strncat(str, userstr, SIZE128-(strlen(str)+1));
    print_str(str);
}

void errorMsg(char* userstr)
{
  char str[SIZE128] = "Error: ";
  strncat(str, userstr, SIZE128-(strlen(str)+1));
  print_str(str);
}

int manageSensorValue(int sensorValue) {
  int ret = sensorValue;
  if ( sensorValue < 0 ) {
    errorMsg("sensor value should be positive");
    exit(1);
  } else if ( sensorValue > 50 ) {
    warningMsg("sensor value greater than 50 (applying threshold)...");
    sensorValue = 50;
  }
  
  return sensorValue;
}

Check Information

Category: Data Neutralization Issues

Version History

Introduced in R2023a