Main Content

CWE Rule 404

Improper Resource Shutdown or Release

Since R2024a

Description

Rule Description

The product does not release or incorrectly releases a resource before it is made available for re-use.

Polyspace Implementation

The rule checker checks for these issues:

  • Invalid deletion of pointer

  • Invalid free of pointer

  • Memory leak

  • Mismatched alloc/dealloc functions on Windows

  • Thread-specific memory leak

Examples

expand all

Issue

This issue occurs when:

  • You release a block of memory with the delete operator but the memory was previously not allocated with the new operator.

  • You release a block of memory with the delete operator using the single-object notation but the memory was previously allocated as an array with the new operator.

This issue applies only to C++ source files.

Risk

The risk depends on the cause of the issue:

  • The delete operator releases a block of memory allocated on the heap. If you try to access a location on the heap that you did not allocate previously, a segmentation fault can occur.

  • If you use the single-object notation for delete on a pointer that is previously allocated with the array notation for new, the behavior is undefined.

The issue can also highlight other coding errors. For instance, you perhaps wanted to use the delete operator or a previous new operator on a different pointer.

Fix

The fix depends on the cause of the issue:

  • In most cases, you can fix the issue by removing the delete statement. If the pointer is not allocated memory from the heap with the new operator, you do not need to release the pointer with delete. You can simply reuse the pointer as required or let the object be destroyed at the end of its scope.

  • In case of mismatched notation for new and delete, correct the mismatch. For instance, to allocate and deallocate a single object, use this notation:

    classType* ptr = new classType;
    delete ptr;

    To allocate and deallocate an array objects, use this notation:

    classType* p2 = new classType[10];
    delete[] p2;

If the issue highlights a coding error such as use of delete or new on the wrong pointer, correct the error.

Example — Deleting Static Memory
void assign_ones(void)
{
    int ptr[10];

    for(int i=0;i<10;i++)
        *(ptr+i)=1;  

    delete[] ptr;    //Noncompliant
}

The pointer ptr is released using the delete operator. However, ptr points to a memory location that was not dynamically allocated.

Correction: Remove Pointer Deallocation

If the number of elements of the array ptr is known at compile time, one possible correction is to remove the deallocation of the pointer ptr.

void assign_ones(void) 
{
    int ptr[10];

    for(int i=0;i<10;i++)
        *(ptr+i)=1;  
}
Correction — Add Pointer Allocation

If the number of array elements is not known at compile time, one possible correction is to dynamically allocate memory to the array ptr using the new operator.

void assign_ones(int num) 
{
    int *ptr = new int[num]; 

    for(int i=0; i < num; i++)
        *(ptr+i) = 1;

    delete[] ptr;
   }
Example — Mismatched new and delete
int main (void)
{
    int *p_scale = new int[5];

    //more code using scal

    delete p_scale; //Noncompliant
}

In this example, p_scale is initialized to an array of size 5 using new int[5]. However, p_scale is deleted with delete instead of delete[]. The new-delete pair does not match. Do not use delete without the brackets when deleting arrays.

Correction — Match delete to new

One possible correction is to add brackets so the delete matches the new [] declaration.

int main (void)
{
    int *p_scale = new int[5];

    //more code using p_scale

    delete[] p_scale;
}
Correction — Match new to delete

Another possible correction is to change the declaration of p_scale. If you meant to initialize p_scale as 5 itself instead of an array of size 5, you must use different syntax. For this correction, change the square brackets in the initialization to parentheses. Leave the delete statement as it is.

int main (void)
{
    int *p_scale = new int(5);

    //more code using p_scale

    delete p_scale;
}
Issue

This issue occurs when a block of memory released using the free function was not previously allocated using malloc, calloc, or realloc.

Risk

The free function releases a block of memory allocated on the heap. If you try to access a location on the heap that you did not allocate previously, a segmentation fault can occur.

The issue can highlight coding errors. For instance, you perhaps wanted to use the free function or a previous malloc function on a different pointer.

Fix

In most cases, you can fix the issue by removing the free statement. If the pointer is not allocated memory from the heap with malloc or calloc, you do not need to free the pointer. You can simply reuse the pointer as required.

If the issue highlights a coding error such as use of free or malloc on the wrong pointer, correct the error.

If the issue occurs because you use the free function to free memory allocated with the new operator, replace the free function with the delete operator.

Example — Invalid Free of Pointer Error
#include <stdlib.h>

void Assign_Ones(void) 
{
  int p[10];
  for(int i=0;i<10;i++)
     *(p+i)=1; 
 
  free(p);    //Noncompliant
  /* Violation: p does not point to dynamically allocated memory */
}

The pointer p is deallocated using the free function. However, p points to a memory location that was not dynamically allocated.

Correction — Remove Pointer Deallocation

If the number of elements of the array p is known at compile time, one possible correction is to remove the deallocation of the pointer p.

#include <stdlib.h>

void Assign_Ones(void)
 {
  int p[10];
  for(int i=0;i<10;i++)
     *(p+i)=1;   
  /* Fix: Remove deallocation of p */
 }
Correction — Introduce Pointer Allocation

If the number of elements of the array p is not known at compile time, one possible correction is to dynamically allocate memory to the array p.

#include <stdlib.h>

void Assign_Ones(int num) 
{
  int *p;
  /* Fix: Allocate memory dynamically to p */
  p=(int*) calloc(10,sizeof(int)); 
  for(int i=0;i<10;i++)
     *(p+i)=1; 
  free(p); 
}
Issue

This issue occurs when you do not free a block of memory allocated through malloc, calloc, realloc, or new. If the memory is allocated in a function, the issue does not occur if:

  • Within the function, you free the memory using free or delete.

  • The function returns the pointer assigned by malloc, calloc, realloc, or new.

  • The function stores the pointer in a global variable or in a parameter.

Risk

Dynamic memory allocation functions such as malloc allocate memory on the heap. If you do not release the memory after use, you reduce the amount of memory available for another allocation. On embedded systems with limited memory, you might end up exhausting available heap memory even during program execution.

Fix

Determine the scope where the dynamically allocated memory is accessed. Free the memory block at the end of this scope.

To free a block of memory, use the free function on the pointer that was used during memory allocation. For instance:

ptr = (int*)malloc(sizeof(int));
...
free(ptr);

It is a good practice to allocate and free memory in the same module at the same level of abstraction. For instance, in this example, func allocates and frees memory at the same level but func2 does not.

void func() {
  ptr = (int*)malloc(sizeof(int));
  {
    ...
  }
  free(ptr);
}

void func2() {
  {
   ptr = (int*)malloc(sizeof(int));
   ...
  }
  free(ptr);
}
See CERT-C Rule MEM00-C.

Example — Dynamic Memory Not Released Before End of Function
#include<stdlib.h>
#include<stdio.h>

void assign_memory(void)
{
    int* pi = (int*)malloc(sizeof(int));
    if (pi == NULL) 
        {
         printf("Memory allocation failed");
         return;
        }


    *pi = 42;
    /* Violation: pi is not freed */
} //Noncompliant

In this example, pi is dynamically allocated by malloc. The function assign_memory does not free the memory, nor does it return pi.

Correction — Free Memory

One possible correction is to free the memory referenced by pi using the free function. The free function must be called before the function assign_memory terminates

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

void assign_memory(void)
{
    int* pi = (int*)malloc(sizeof(int));
    if (pi == NULL) 
        {
         printf("Memory allocation failed");
         return;
        }
    *pi = 42;

    /* Fix: Free the pointer pi*/
    free(pi);                   
}
Correction — Return Pointer from Dynamic Allocation

Another possible correction is to return the pointer pi. Returning pi allows the function calling assign_memory to free the memory block using pi.

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

int* assign_memory(void)
{
    int* pi = (int*)malloc(sizeof(int));
    if (pi == NULL) 
        {
            printf("Memory allocation failed");
            return(pi);
        }
    *pi = 42;

    /* Fix: Return the pointer pi*/
    return(pi);                   
}
Example — Memory Leak with New/Delete
#define NULL '\0'

void initialize_arr1(void)
{
    int *p_scalar = new int(5);
} //Noncompliant

void initialize_arr2(void)
{
    int *p_array = new int[5];
} //Noncompliant

In this example, the functions create two variables, p_scalar and p_array, using the new keyword. However, the functions end without cleaning up the memory for these pointers. Because the functions used new to create these variables, you must clean up their memory by calling delete at the end of each function.

Correction — Add Delete

To correct this error, add a delete statement for every new initialization. If you used brackets [] to instantiate a variable, you must call delete with brackets as well.

#define NULL '\0'

void initialize_arrs(void)
{
    int *p_scalar = new int(5); 
    int *p_array = new int[5];  

    delete p_scalar;
    p_scalar = NULL;

    delete[] p_array;
    p_scalar = NULL;
}
Issue

This issue occurs when you use a Windows® deallocation function that is not properly paired to its corresponding allocation function.

Risk

Deallocating memory with a function that does not match the allocation function can cause memory corruption or undefined behavior. If you are using an older version of Windows, the improper function can also cause compatibility issues with newer versions.

Fix

Properly pair your allocation and deallocation functions according to the functions listed in this table.

Allocation FunctionDeallocation Function
malloc()free()
realloc()free()
calloc()free()
_aligned_malloc()_aligned_free()
_aligned_offset_malloc()_aligned_free()
_aligned_realloc()_aligned_free()
_aligned_offset_realloc()_aligned_free()
_aligned_recalloc()_aligned_free()
_aligned_offset_recalloc()_aligned_free()
_malloca()_freea()
LocalAlloc()LocalFree()
LocalReAlloc()LocalFree()
GlobalAlloc()GlobalFree()
GlobalReAlloc()GlobalFree()
VirtualAlloc()VirtualFree()
VirtualAllocEx()VirtualFreeEx()
VirtualAllocExNuma()VirtualFreeEx()
HeapAlloc()HeapFree()
HeapReAlloc()HeapFree()

Example — Memory Deallocated with Incorrect Function
#ifdef _WIN32_
#include <windows.h>
#else
#define _WIN32_
typedef void *HANDLE;
typedef HANDLE HGLOBAL;
typedef HANDLE HLOCAL;
typedef unsigned int UINT;
extern HLOCAL LocalAlloc(UINT uFlags, UINT uBytes);
extern HLOCAL LocalFree(HLOCAL hMem);
extern HGLOBAL GlobalFree(HGLOBAL hMem);
#endif

#define SIZE9 9


void func(void)
{
	/* Memory allocation */
    HLOCAL p = LocalAlloc(0x0000, SIZE9);
	
    if (p) {
		/* Memory deallocation. */
        GlobalFree(p); //Noncompliant
		
    }
}
     
      

In this example, memory is allocated with LocallAlloc(). The program then erroneously uses GlobalFree() to deallocate the memory.

Correction — Properly Pair Windows Allocation and Deallocation Functions

When you allocate memory with LocalAllocate(), use LocalFree() to deallocate the memory.

#ifdef _WIN32_
#include <windows.h>
#else
#define _WIN32_
typedef void *HANDLE;
typedef HANDLE HGLOBAL;
typedef HANDLE HLOCAL;
typedef unsigned int UINT;
extern HLOCAL LocalAlloc(UINT uFlags, UINT uBytes);
extern HLOCAL LocalFree(HLOCAL hMem);
extern HGLOBAL GlobalFree(HGLOBAL hMem);
#endif

#define SIZE9 9
void func(void)
{
	/* Memory allocation */
    HLOCAL p = LocalAlloc(0x0000, SIZE9);
    if (p) {
		/* Memory deallocation. */
        LocalFree(p);  
    }
} 
Issue

This checker is deactivated in a default Polyspace® as You Code analysis. See Checkers Deactivated in Polyspace as You Code Analysis (Polyspace Access).

This issue occurs when you do not free thread-specific dynamically allocated memory before the end of a thread.

To create thread-specific storage, you generally do these steps:

  1. You create a key for thread-specific storage.

  2. You create the threads.

  3. In each thread, you allocate storage dynamically and then associate the key with this storage.

    After the association, you can read the stored data later using the key.

  4. Before the end of the thread, you free the thread-specific memory using the key.

The checker flags execution paths in the thread where the last step is missing.

The checker works on these families of functions:

  • tss_get and tss_set (C11)

  • pthread_getspecific and pthread_setspecific (POSIX)

Risk

The data stored in the memory is available to other processes even after the threads end (memory leak). Besides security vulnerabilities, memory leaks can shrink the amount of available memory and reduce performance.

Fix

Free dynamically allocated memory before the end of a thread.

You can explicitly free dynamically allocated memory with functions such as free.

Alternatively, when you create a key, you can associate a destructor function with the key. The destructor function is called with the key value as argument at the end of a thread. In the body of the destructor function, you can free any memory associated with the key. If you use this method, Bug Finder still reports a violation. Ignore this violation with appropriate comments. See:

Example — Memory Not Freed at End of Thread
#include <threads.h>
#include <stdlib.h>
 
/* Global key to the thread-specific storage */
tss_t key; //Testing
enum { MAX_THREADS = 3 };
 

int add_data(void) {
  int *data = (int *)malloc(2 * sizeof(int));
  if (data == NULL) {
    return -1;  /* Report error  */
  }
  data[0] = 0;
  data[1] = 1;
 
  if (thrd_success != tss_set(key, (void *)data)) {
    /* Handle error */
  }
  return 0;
}
 
void print_data(void) {
  /* Get this thread's global data from key */
  int *data = tss_get(key);
 
  if (data != NULL) {
    /* Print data */
  }
}
 
int func(void *dummy) {
  if (add_data() != 0) {
    return -1;  /* Report error */ //Noncompliant
  }
  print_data();
  return 0; //Noncompliant
}
 
int main(void) {
  thrd_t thread_id[MAX_THREADS];
 
  /* Create the key before creating the threads */
  if (thrd_success != tss_create(&key, NULL)) {
    /* Handle error */
  }
 
  /* Create threads that would store specific storage */
  for (size_t i = 0; i < MAX_THREADS; i++) {
    if (thrd_success != thrd_create(&thread_id[i], func, NULL)) {
      /* Handle error */
    }
  }
 
  for (size_t i = 0; i < MAX_THREADS; i++) {
    if (thrd_success != thrd_join(thread_id[i], NULL)) {
      /* Handle error */
    }
  }
 
  tss_delete(key);
  return 0;
}

In this example, the start function of each thread func calls two functions:

  • add_data: This function allocates storage dynamically and associates the storage with a key using the tss_set function.

  • print_data: This function reads the stored data using the tss_get function.

At the points where func returns, the dynamically allocated storage has not been freed.

Correction — Free Dynamically Allocated Memory Explicitly

One possible correction is to free dynamically allocated memory explicitly before leaving the start function of a thread.

In this corrected version, a violation still appears on the return statement in the error handling section of func. The violation cannot occur in practice because the error handling section is entered only if dynamic memory allocation fails. Ignore this remaining violation with appropriate comments. See:

#include <threads.h>
#include <stdlib.h>
 
/* Global key to the thread-specific storage */
tss_t key;
enum { MAX_THREADS = 3 };
 

int add_data(void) {
  int *data = (int *)malloc(2 * sizeof(int));
  if (data == NULL) {
    return -1;  /* Report error  */
  }
  data[0] = 0;
  data[1] = 1;
 
  if (thrd_success != tss_set(key, (void *)data)) {
    /* Handle error */
  }
  return 0;
}
 
void print_data(void) {
  /* Get this thread's global data from key */
  int *data = tss_get(key);
 
  if (data != NULL) {
    /* Print data */
  }
}
 
int func(void *dummy) {
  if (add_data() != 0) {
    return -1;  /* Report error */ //Noncompliant
  }
  print_data();
  free(tss_get(key));
  return 0;
}
 
int main(void) {
  thrd_t thread_id[MAX_THREADS];
 
  /* Create the key before creating the threads */
  if (thrd_success != tss_create(&key, NULL)) {
    /* Handle error */
  }
 
  /* Create threads that would store specific storage */
  for (size_t i = 0; i < MAX_THREADS; i++) {
    if (thrd_success != thrd_create(&thread_id[i], func, NULL)) {
      /* Handle error */
    }
  }
 
  for (size_t i = 0; i < MAX_THREADS; i++) {
    if (thrd_success != thrd_join(thread_id[i], NULL)) {
      /* Handle error */
    }
  }
 
  tss_delete(key);
  return 0;
}

Check Information

Category: Others

Version History

Introduced in R2024a