Main Content

Thread-specific memory leak

Dynamically allocated thread-specific memory not freed before end of thread

Description

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

This defect 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:

Examples

expand all

#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 */
  }
  print_data();
  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;
}

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;  /* Report error */
  }
  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;
}

Result Information

Group: Concurrency
Language: C | C++
Default: Off
Command-Line Syntax: THREAD_MEM_LEAK
Impact: Medium

Version History

Introduced in R2018b