Main Content

CWE Rule 690

Unchecked Return Value to NULL Pointer Dereference

Since R2023a

Description

Rule Description

The product does not check for an error after calling a function that can return with a NULL pointer if the function fails, which leads to a resultant NULL pointer dereference.

Polyspace Implementation

The rule checker checks for these issues:

  • Returned value of a sensitive function not checked

  • Tainted NULL or non-null-terminated string

  • Unprotected dynamic memory allocation

Examples

expand all

Issue

This issue occurs when you call sensitive standard functions that return information about possible errors and you do one of the following:

  • Ignore the return value.

    You simply do not assign the return value to a variable, or explicitly cast the return value to void.

  • Use an output from the function (return value or argument passed by reference) without testing the return value for errors.

The checker considers a function as sensitive if the function call is prone to failure because of reasons such as:

  • Exhausted system resources (for example, when allocating resources).

  • Changed privileges or permissions.

  • Tainted sources when reading, writing, or converting data from external sources.

  • Unsupported features despite an existing API.

The checker only considers functions where the return value indicates if the function completed without errors.

Some of these functions can perform critical tasks such as:

  • Set privileges (for example, setuid)

  • Create a jail (for example, chroot)

  • Create a process (for example, fork)

  • Create a thread (for example, pthread_create)

  • Lock or unlock mutex (for example, pthread_mutex_lock)

  • Lock or unlock memory segments (for example, mlock)

Risk

If you do not check the return value of functions that perform sensitive tasks and indicate error information through their return values, your program can behave unexpectedly. Errors from these functions can propagate throughout the program causing incorrect output, security vulnerabilities, and possibly system failures.

Fix

Before continuing with the program, test the return value of critical sensitive functions.

For sensitive functions that are not critical, you can explicitly ignore a return value by casting the function to void. Polyspace® does not raise this defect for sensitive functions cast to void. This resolution is not accepted for critical sensitive functions because they perform more vulnerable tasks.

Example — Sensitive Function Return Ignored
#include <pthread.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>

void initialize() {
    pthread_attr_t attr;

    pthread_attr_init(&attr);//Noncompliant 
}
int read_file(int argc, char *argv[])
{
  FILE *in;
  if (argc != 2) {
    /* Handle error */
  }

  in = fmemopen (argv[1], strlen (argv[1]), "r");   
  return 0; //Noncompliant

}

This example shows calls to the sensitive POSIX functions pthread_attr_init and fmemopen. Their return values are ignored, causing defect.

Correction — Cast Function to (void)

One possible correction is to cast the function to void. This fix informs Polyspace and any reviewers that you are explicitly ignoring the return value of the sensitive function.

#include <pthread.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>

void initialize() {
    pthread_attr_t attr;

    (void)pthread_attr_init(&attr);//Compliant 
}
int read_file(int argc, char *argv[])
{
  FILE *in;
  if (argc != 2) {
    /* Handle error */
  }

  (void)fmemopen (argv[1], strlen (argv[1]), "r"); //Compliant
  
  return 0; 
}
Correction — Test Return Value

One possible correction is to test the return value of pthread_attr_init and fmemopen to check for errors.

#include <pthread.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>

void initialize() {
    pthread_attr_t attr;

    int result = pthread_attr_init(&attr);//Compliant 
	if(result != 0){
		//Handle fatal error
	} 
}
int read_file(int argc, char *argv[])
{
  FILE *in;
  if (argc != 2) {
    /* Handle error */
  }

  in = fmemopen (argv[1], strlen (argv[1]), "r"); 
  if (in==NULL){
	  // Handle error
  }
  return 0;//Compliant 
}
Example — Critical Function Return Ignored
#include <pthread.h>
extern void *start_routine(void *);

void returnnotchecked() {
    pthread_t thread_id;
    pthread_attr_t attr;
    void *res;

    (void)pthread_attr_init(&attr);
    (void)pthread_create(&thread_id, &attr, &start_routine, ((void *)0));  //Noncompliant
    pthread_join(thread_id,  &res);  //Noncompliant
}

In this example, two critical functions are called: pthread_create and pthread_join. The return value of the pthread_create is ignored by casting to void, but because pthread_create is a critical function (not just a sensitive function), Polyspace does not ignore this Return value of a sensitive function not checked defect. The other critical function, pthread_join, returns value that is ignored implicitly. pthread_join uses the return value of pthread_create, which was not checked.

Correction — Test the Return Value of Critical Functions

The correction for this defect is to check the return value of these critical functions to verify the function performed as expected.

#include <pthread.h>
#include <stdlib.h>
#define fatal_error() abort()

extern void *start_routine(void *);

void returnnotchecked() {
    pthread_t thread_id;
    pthread_attr_t attr;
    void *res;
    int result;

    (void)pthread_attr_init(&attr);
    result = pthread_create(&thread_id, &attr, &start_routine, NULL);
    if (result != 0) {
        /* Handle error */
        fatal_error();
    }

    result = pthread_join(thread_id,  &res);
    if (result != 0) {
        /* 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];
	int n = read(0,userstr,MAX);
	char str[SIZE128] = "Warning: ";
	if (n != -1)
         strncat(str, userstr, SIZE128-(strlen(str)+1));//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
		// - 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];
	int n = read(0,userstr,MAX);
	char str[SIZE128] = "Warning: ";
	if (n != -1 && 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;
}
Issue

This issue occurs when you access dynamically allocated memory without first checking if the prior memory allocation succeeded.

Risk

When memory is dynamically allocated using malloc, calloc, or realloc, it returns a value NULL if the requested memory is not available. If the code following the allocation accesses the memory block without checking for this NULL value, this access is not protected from failures.

Fix

Check the return value of malloc, calloc, or realloc for NULL before accessing the allocated memory location.

int *ptr = malloc(size * sizeof(int));

if(ptr) /* Check for NULL */ 
{
   /* Memory access through ptr */
}

Example — Unprotected dynamic memory allocation error
#include <stdlib.h>

void Assign_Value(void) 
{
  int* p = (int*)calloc(5, sizeof(int));

  *p = 2;  //Noncompliant
  /* Defect: p is not checked for NULL value */

  free(p);
}

If the memory allocation fails, the function calloc returns NULL to p. Before accessing the memory through p, the code does not check whether p is NULL

Correction — Check for NULL Value

One possible correction is to check whether p has value NULL before dereference.

#include <stdlib.h>

void Assign_Value(void)
 {
   int* p = (int*)calloc(5, sizeof(int));

   /* Fix: Check if p is NULL */
   if(p!=NULL) *p = 2; 

   free(p);
 }
Example — Unprotected dynamic memory allocation error only on dereference
#include <stdlib.h>
#include<string.h>
typedef struct recordType {
    const char* id;
    const char* data;
} RECORD;

RECORD* MakerecordType(const char *id,unsigned int size){
    RECORD *rec = (RECORD *)calloc(1, sizeof(RECORD));
    rec->id = strdup(id);  //Noncompliant

    const char *newData = (char *)calloc(1, size);
    rec->data = newData;
    return rec;
}

In this example, the checker raises a defect when you dereference the pointer rec without checking for a NULL value from the prior dynamic memory allocation.

A similar issue happens with the pointer newData. However, a defect is not raised because the pointer is not dereferenced but simply copied over to rec->data. Simply copying over a possibly null pointer is not an issue by itself. For instance, callers of the recordType_new function might check for NULL value of rec->data before dereferencing, thereby avoiding a null pointer dereference.

Check Information

Category: Others

Version History

Introduced in R2023a