メインコンテンツ

CWE Rule 401

Missing Release of Memory after Effective Lifetime

R2023a 以降

説明

ルールの説明

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

Polyspace 実装

ルール チェッカーは以下の問題をチェックします。

  • メモリ リーク

  • スレッド固有のメモリ リーク

すべて展開する

問題

この問題は、malloccallocrealloc、または new を介して割り当てられたメモリのブロックが解放されなかった場合に発生します。メモリが関数内で割り当てられる場合、以下の条件を満たせば欠陥は生じません。

  • 関数内で、free または delete を使用してメモリが解放される。

  • 関数が malloccallocrealloc または new によって割り当てられたポインターを返す。

  • 関数がグローバル変数またはパラメーターにポインターを格納する。

リスク

malloc などの動的メモリ割り当て関数はヒープ上のメモリを割り当てます。使用後にメモリを解放しないと、別の割り当てに使用できるメモリの量が減少します。メモリに制限がある組み込みシステムでは、プログラムの実行中でも、使用可能なヒープ メモリを使い果たす可能性があります。

修正方法

動的に割り当てられたメモリへのアクセスがあるスコープを確認します。このスコープの最後でメモリ ブロックを解放します。

メモリのブロックを解放するには、メモリの割り当て時に使用したポインターに対して関数 free を使用します。次に例を示します。

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

同じモジュール内のメモリは同じ抽象化レベルで割り当てと解放を行うことをお勧めします。次の例の場合、func はメモリの割り当てと解放を同じレベルで行っていますが、func2 はそうではありません。

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

void func2() {
  {
   ptr = (int*)malloc(sizeof(int));
   ...
  }
  free(ptr);
}
CERT-C Rule MEM00-C を参照してください。

例 — 関数の終了前に動的メモリが未解放
#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

この例では、malloc によって pi が動的に割り当てられています。関数 assign_memory はメモリを解放せず、pi も返しません。

修正 — メモリを解放

1 つの修正方法として、pi が参照するメモリを関数 free を使用して解放することができます。関数 free は関数 assign_memory が終了する前に呼び出さなければなりません。

#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);                   
}
修正 — 動的割り当てからポインターを返す

もう 1 つの修正方法は、ポインター pi を返すことです。pi を返すことにより、assign_memory を呼び出している関数が 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);                   
}
例 — 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

この例では、関数により 2 つの変数 p_scalar および p_array が、キーワード new を使用して作成されています。しかしこれらの関数は、これらのポインターについてメモリをクリーン アップせずに終了しています。これらの関数はこれらの変数の作成に new を使用したので、そのメモリは、各関数の最後で delete を呼び出してクリーン アップしなければなりません。

修正 — delete を追加

このエラーを修正するため、delete ステートメントを new の初期化ごとに追加します。大かっこ [] を使用して変数をインスタンス化した場合は、delete の呼び出しにも大かっこを使用しなければなりません。


#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;
}
問題

このチェッカーは、既定の Polyspace® as You Code 解析では非アクティブにされますPolyspace as You Code 解析で非アクティブにされるチェッカー (Polyspace Access)を参照してください。

この問題は、動的に割り当てられたスレッド固有のメモリをスレッドの終了前に開放しなかった場合に発生します。

スレッド固有のストレージを作成するには、通常、以下の手順を行います。

  1. スレッド固有のストレージのキーを作成する。

  2. スレッドを作成する。

  3. 各スレッドで、ストレージを動的に割り当ててから、このストレージとキーを関連付ける。

    関連付けたら、格納されたデータはキーを使用して後から読み取ることができます。

  4. スレッドの終了前に、キーを使用してスレッド固有のメモリを解放する。

チェッカーは、最後の手順が不足しているスレッドの実行パスにフラグを設定します。

チェッカーは、以下の関数のファミリに対して機能します。

  • tss_get および tss_set (C11)

  • pthread_getspecific および pthread_setspecific (POSIX)

リスク

メモリに格納されたデータは、スレッドの終了後も他のプロセスから利用できます (メモリ リーク)。メモリ リークはセキュリティの脆弱性になるだけでなく、使用可能なメモリの量を縮小し、パフォーマンスを低下させる可能性があります。

修正方法

スレッドの終了前に動的に割り当てられたメモリを解放します。

動的に割り当てられたメモリは、free などの関数を使用して明示的に開放できます。

または、キーを作成するときにデストラクター関数とキーを関連付けることができます。デストラクター関数は、キー値を引数に指定してスレッドの最後に呼び出されます。デストラクター関数の本体で、キーに関連付けられたメモリを解放することができます。この方法を使用している場合でも、Bug Finder は欠陥にフラグを設定します。適切なコメントを使用してこの欠陥は無視します。詳細は、以下を参照してください。

例 — スレッドの終了時にメモリが未解放
#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;
}

この例では、各スレッドの開始関数 func で次の 2 つの関数を呼び出しています。

  • add_data: この関数はストレージを動的に割り当て、関数 tss_set を使用してそのストレージとキーを関連付けます。

  • print_data: この関数は関数 tss_get を使用して、格納されているデータを読み取ります。

func が戻った時点では、動的に割り当てられたストレージが解放されていません。

修正 — 動的に割り当てられたメモリを明示的に開放

1 つの修正方法として、スレッドの開始関数が終わる前に、動的に割り当てられたメモリを明示的に開放します。訂正されたバージョンでは変更が強調表示されています。

この訂正されたバージョンでも、func のエラー処理セクションの return ステートメントには相変わらず欠陥が表示されます。エラー処理セクションに入るのは動的なメモリ割り当てが失敗した場合に限られるため、この欠陥は実際には発生しません。適切なコメントを使用してこの残りの欠陥は無視します。詳細は、以下を参照してください。

#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;
}

チェック情報

カテゴリ: その他

バージョン履歴

R2023a で導入