Main Content

CERT C++: ERR33-C

Detect and handle standard library errors

Description

Rule Definition

Detect and handle standard library errors.1

Polyspace Implementation

The rule checker checks for these issues:

  • Errno not checked.

  • Returned value of a sensitive standard function not checked.

  • Unprotected dynamic memory allocation.

Examples

expand all

Issue

Errno not checked occurs when you call a function that sets errno to indicate error conditions, but do not check errno after the call. For these functions, checking errno is the only reliable way to determine if an error occurred.

Functions that set errno on errors include:

  • fgetwc, strtol, and wcstol.

    For a comprehensive list of functions, see documentation about errno.

  • POSIX® errno-setting functions such as encrypt and setkey.

Risk

To see if the function call completed without errors, check errno for error values.

The return values of these errno-setting functions do not indicate errors. The return value can be one of the following:

  • void

  • Even if an error occurs, the return value can be the same as the value from a successful call. Such return values are called in-band error indicators.

You can determine if an error occurred only by checking errno.

For instance, strtol converts a string to a long integer and returns the integer. If the result of conversion overflows, the function returns LONG_MAX and sets errno to ERANGE. However, the function can also return LONG_MAX from a successful conversion. Only by checking errno can you distinguish between an error and a successful conversion.

Fix

Before calling the function, set errno to zero.

After the function call, to see if an error occurred, compare errno to zero. Alternatively, compare errno to known error indicator values. For instance, strtol sets errno to ERANGE to indicate errors.

The error message in the Polyspace® result shows the error indicator value that you can compare to.

Example - errno Not Checked After Call to strtol
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>

int main(int argc, char *argv[]) {
    char *str, *endptr;
    int base;
    
    str = argv[1];
    base = 10;
    
    long val = strtol(str, &endptr, base); //Noncompliant
    printf("Return value of strtol() = %ld\n", val);
}

You are using the return value of strtol without checking errno.

Correction — Check errno After Call

Before calling strtol, set errno to zero. After a call to strtol, check the return value for LONG_MIN or LONG_MAX and errno for ERANGE.

#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<limits.h>

int main(int argc, char *argv[]) {
    char *str, *endptr;
    int base;
    
    str = argv[1];
    base = 10;
    
    errno = 0;
    long val = strtol(str, &endptr, base);
    if((val == LONG_MIN || val == LONG_MAX) && errno == ERANGE) {
         printf("strtol error");
         exit(EXIT_FAILURE);
    }        
    printf("Return value of strtol() = %ld\n", val);
}
Issue

Returned value of a sensitive standard function not checked occurs when you call sensitive standard functions, but you:

  • Ignore the return value.

  • Use an output or a return value without testing the validity of the return value.

For this defect, two type of functions are considered: sensitive and critical sensitive.

A sensitive function is a standard function that can encounter:

  • 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

A critical sensitive function is a sensitive function that performs one of these critical or vulnerable tasks:

  • Set privileges (for example, setuid)

  • Create a jail (for example, chroot)

  • Create a process (for example, fork)

  • Create a thread (for example, thrd_create)

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

Risk

If you do not check the return value of functions that perform sensitive or critical sensitive tasks, 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, 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<cstdlib>
#include<cstdio>
#include <wchar.h>
#include <locale.h>
void initialize(size_t n, size_t* size, wchar_t *wcs, const char *utf8) {

	scanf("%d",&n); //Noncompliant
	setlocale (LC_CTYPE, "en_US.UTF-8");   //Noncompliant
	*size = mbstowcs (wcs, utf8, n);
}

This example shows a call to the sensitive function scanf(). The return value of scanf() is ignored, causing a defect. Similarly, the pointer returned by setlocale is not checked. When setlocal returns a NULL pointer, the call to mbstowcs might fail or produce unexpected results. Polyspace flags these calls to sensitive functions when their returns are not checked.

Correction — Cast Function to (void)

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

#include<cstdlib>
#include<cstdio>
#include <wchar.h>
#include <locale.h>
void initialize(size_t n, size_t* size, wchar_t *wcs, const char *utf8) {

	(void)scanf("%d",&n); //Compliant
	(void)setlocale (LC_CTYPE, "en_US.UTF-8");   //Compliant
	*size = mbstowcs (wcs, utf8, n);
}
Correction — Test Return Value

One possible correction is to test the return value of scanf and setlocale to check for errors.

#include<cstdlib>
#include<cstdio>
#include <wchar.h>
#include <locale.h>
void initialize(size_t n, size_t* size, wchar_t *wcs, const char *utf8) {

	int flag = scanf("%d",&n); 
	if(flag>0){ //Compliant
		// action
	}
	char* status = setlocale (LC_CTYPE, "en_US.UTF-8");   
	if(status!=NULL){//Compliant
		*size = mbstowcs (wcs, utf8, n);
	}
	
}
Example — Unchecked Dynamic Memory Allocation
#include <stddef.h>
#include <stdlib.h>

void unchecked_memory_allocation(void) {
  int * p = (int*)calloc(5, sizeof(int));// C-style allocation  
  *p = 2;                                   //Noncompliant
  //...
  delete[] p;
}

In this example, memory is dynamically allocated for the pointer *p. The pointer is then used without checking the output of the dynamic memory allocation operation. Polyspace raises this defect when pointers are used after an unchecked dynamic memory allocation operation.

Correction — Check Output of Dynamic Memory Allocation

The correction for this defect is to check the return value of the operation new to verify that the function performed as expected.

#include <stddef.h>
#include <stdlib.h>

void checked_memory_allocation(void) {
	int * p = new int[5];  
	if(p==NULL){// Check output of new
		//Handle memory allocation error
	}else{
		*p = 2; //Compliant                                   
		//...
		delete[] p;
	}

}
Issue

Unprotected dynamic memory allocation occurs when you do not check after dynamic memory allocation whether the 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.

#DEFINE SIZE 8;

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); 
} /*Defect: p is not checked for NULL before deallocating*/

If the memory allocation fails, the function calloc returns NULL to p. Before accessing the memory through p or freeing p, the code does not check whether p is NULL. These operations might result in memory leaks.

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);
 }
Issue

Pointer overwritten during reallocation occurs when you overwrite the original pointer by the return value of realloc(). For instance:

p = realloc(p,SIZE);

Risk

The function realloc() returns a NULL value when memory allocation fails. In the preceding code, because you overwrite p by the return of realloc(), it becomes NULL when the reallocation operation fails. You lose the connection between the original memory block and p, resulting in a memory leak.

Fix

When reallocating pointers, preserve the original pointer. For instance, you might use a temporary variable to store the reallocated memory.

Example — Avoid Overwriting Original Pointer When Reallocating Memory
#include <stdlib.h>

void foo (int* ptrI, size_t new_size)
{

  if (new_size == 0) {
    /* Handle error */
    return;
  }

  ptrI = (int*)realloc (ptrI, new_size);   //Noncompliant

  if (ptrI == NULL) {
    /* Handle error */
    return;
  }
}

Overwriting the pointer ptrI by the pointer returned by realloc destroys the association between ptrI and the original memory block. If realloc fails, such overwriting might cause a memory leak and data loss.

Correction — Store Reallocated Memory in Temporary Variable

When reallocating a pointer, use a temporary variable to hold the reallocated memory. Before assigning the temporary variable to ptrI, check it for NULL value to avoid memory leaks and data loss.

#include <stdlib.h>

void foo (int* ptrI, size_t new_size)
{
int* temp;
  if (new_size == 0) {
    /* Handle error */
    return;
  }

  temp = (int*)realloc (ptrI, new_size);   //Compliant

  if (temp == NULL) {
    /* Handle error */
    return;
  }else{
	  ptrI = temp;
  }
}

Check Information

Group: 08. Exceptions and Error Handling (ERR)

Version History

Introduced in R2019a

expand all


1 This software has been created by MathWorks incorporating portions of: the “SEI CERT-C Website,” © 2017 Carnegie Mellon University, the SEI CERT-C++ Web site © 2017 Carnegie Mellon University, ”SEI CERT C Coding Standard – Rules for Developing safe, Reliable and Secure systems – 2016 Edition,” © 2016 Carnegie Mellon University, and “SEI CERT C++ Coding Standard – Rules for Developing safe, Reliable and Secure systems in C++ – 2016 Edition” © 2016 Carnegie Mellon University, with special permission from its Software Engineering Institute.

ANY MATERIAL OF CARNEGIE MELLON UNIVERSITY AND/OR ITS SOFTWARE ENGINEERING INSTITUTE CONTAINED HEREIN IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.

This software and associated documentation has not been reviewed nor is it endorsed by Carnegie Mellon University or its Software Engineering Institute.