JPCERT コーディネーションセンター

CON30-C. スレッド固有のメモリを適切に解放する

CON30-C. スレッド固有のメモリを適切に解放する

tss_create() 関数はキーによって識別される、スレッド固有のメモリ領域を参照するポインタを作成する。スレッドは、スレッド固有のメモリ領域を割り当て、tss_set() 関数呼出しにより、割り当てたメモリ領域を一意に識別するキーと関連づけることができる。メモリ領域が適切に解放されなければメモリリークが発生するだろう。スレッド固有のメモリは確実に解放すること。

違反コード

次の違反コード例では、各スレッドは get_data() の中で動的にメモリを割り当て、add_data() 関数の中で tss_set() を呼び出すことで、割り当てたメモリをグローバルなキーに関連づける。スレッドが終了すると、このメモリはリークする。

#include <threads.h>
#include <stdlib.h>

/* スレッド固有のメモリを指すグローバルなキー */
tss_t key;
enum { MAX_THREADS = 3 };

int *get_data(void) {
  int *arr = (int *)malloc(2 * sizeof(int));
  if (arr == NULL) {
    return arr;  /* Report error  */
  }
  arr[0] = 10;
  arr[1] = 42;
  return arr;
}

int add_data(void) {
  int *data = get_data();
  if (data == NULL) {
    return -1;	/* Report error */
  }

  if (thrd_success != tss_set(key, (void *)data)) {
    /* エラー処理 */
  }
  return 0;
}

void print_data(void) {
  /* キーからこのスレッドのグローバルデータを取得する */
  int *data = tss_get(key);

  if (data != NULL) {
    /* データをプリントする */
  } 
}

int function(void *dummy) {
  if (add_data() != 0) {
    return -1;	/* エラーを通知する */
  }
  print_data();
  return 0;
}

int main(void) {
  thrd_t thread_id[MAX_THREADS];

  /* スレッドを作成する前にキーを作成する */
  if (thrd_success != tss_create(&key, NULL)) {
    /* エラー処理 */
  }

  /* 特定のメモリを格納するスレッドを作成する */
  for (size_t i = 0; i < MAX_THREADS; i++) {
    if (thrd_success != thrd_create(&thread_id[i], function, NULL)) {
      /*エラー処理 */
    }
  }

  for (size_t i = 0; i < MAX_THREADS; i++) {
    if (thrd_success != thrd_join(thread_id[i], NULL)) {
      /* エラー処理 */
    }
  }

  tss_delete(key);
  return 0;
}
適合コード

次の適合コードでは、各スレッドは終了する前に、tss_get() 関数が返すスレッド固有のメモリを明示的に解放している。

#include <threads.h>
#include <stdlib.h>
 
/* スレッド固有のメモリを指すグローバルなキー */
tss_t key;
 
int function(void *dummy) {
  if (add_data() != 0) {
    return -1;	/* Report error */
  }
  print_data();
  free(tss_get(key));
  return 0;
}

/* ... 他の関数には変更なし */
適合コード

次の適合コードでは、スレッド固有のメモリを自動的に解放するデストラクタを、tss_create() の呼び出しで登録している。

#include <threads.h>
#include <stdlib.h>

/* スレッド固有のメモリに対応するグローバルキー */
tss_t key;
enum { MAX_THREADS = 3 };

/* ... 他の関数に変更はない */

void destructor(void *data) {
  free(data);
}

int main(void) {
  thrd_t thread_id[MAX_THREADS];

  /* スレッドを作成する前にキーを作成する */
  if (thrd_success != tss_create(&key, destructor)) {
    /* エラー処理 */
  }

  /* スレッド固有のメモリを格納するスレッドを作成する */
  for (size_t i = 0; i < MAX_THREADS; i++) {
    if (thrd_success != thrd_create(&thread_id[i], function, NULL)) {
      /* エラー処理 */
    }
  }

  for (size_t i = 0; i < MAX_THREADS; i++) {
    if (thrd_success != thrd_join(thread_id[i], NULL)) {
      /* エラー処理 */
    }
  }

  tss_delete(key);
  return 0;
}

Defect Report #416 には「この標準はスレッド固有のデータのキー(tss_create 関数を使って作成される)に対応するデストラクタが呼ばれるかどうか、あるいはいつ呼ばれるかを規定しない」と書かれている。WG14 委員会における議論から導かれる重要な見解は、スレッドに関する規定を熟慮の結果あえて厳密に規定しないことで、様々なOS上での実装が可能になるよう最大限の機会を提供することであった。ここから言えることは、この適合コードあるいはスレッド固有のデータキーにデストラクタを使う同様の解決策を採用する前に、処理系ごとにドキュメントに当たることが重要であるということである。

リスク評価

スレッド固有のオブジェクトを解放しないとメモリリークやサービス運用妨害攻撃につながるおそれがある。

ルール

深刻度

可能性

修正コストt

優先度

レベル

CON30-C

P4

L3

翻訳元

これは以下のページを翻訳したものです。

CON30-C. Clean up thread-specific storage (revision 58)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter