Main Content

CWE Rule 401

Missing Release of Memory after Effective Lifetime

Since R2023a

Description

Rule Description

The software does not sufficiently track and release allocated memory after it has been used, which slowly consumes remaining memory.

Polyspace Implementation

The rule checker checks for these issues:

  • Memory leak

  • Thread-specific memory leak

Examples

expand all

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 defect 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;
    /* Defect: 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 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 flags a defect. Ignore this defect 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;
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;  //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. See the highlighted change in the corrected version.

In this corrected version, a defect still appears on the return statement in the error handling section of func. The defect cannot occur in practice because the error handling section is entered only if dynamic memory allocation fails. Ignore this remaining defect 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;  //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 R2023a