CONC.DL

デッドロック

マルチスレッドプログラムでのデッドロックとは、2 つ以上のスレッドが互いにブロックし合い、それぞれが他方が終了するのを待機する状況を指します。これは一般に、各スレッドが他のスレッドが必要とするリソースを保持し、各スレッドがそのリソースを必要とするまでどれもブロック解除ができない状況の、異常なロック競合によって生じます。

デッドロックは一般には、異常に予約されたロックの循環チェーンによって起こります。たとえば、Thread 1 は、ロック B の前に予約されたロック A を呼び出すロック予約パターンを持つコードを実行します。一方、Thread 2 は (同じである可能性のある) コードを実行しますが、これはロック A の前に予約されたロック B を呼び出す別のロック予約パターンを持っています。これら 2 つのスレッドが実行で競合すると、Thread 2 がすでに予約した後で、Thread 1 がロック B を要求する可能性があります。Thread 2 は、Thread 1 がすでに予約済みのため、ロック A を単にブロックする目的だけで要求する可能性があります。

チェッカー CONC.DL はデッドロックのインスタンスを検出します。

脆弱性とリスク

この種の循環ロック参照はプログラムの行き詰まり、UI がアクティブにならない、デバイスが応答しない、などの問題を引き起こす可能性があります。現場でのこのような問題のデバッグにより、開発者は数週間の時間を浪費し、顧客にはイライラだけが残ります。

軽減と防止

ロック競合を回避するための指針:

  • コードのロックされるセクションを、できるだけ小さく、できるだけ単純にして、理解しやすいようにします。
  • データ競合のような同時実行問題を起こす可能性のあるコードのセクションをロックしないこと。
  • 循環待ち条件は、是が非でも避けること。
  • いくつかのロックを、通常はエスカレートしたガードパターンで使用する場合、環境ごとに厳密に同じエスカレーションが実行されることを必ず確認する。

脆弱コード例 1

コピー
  #include <stdio.h>
  #include <pthread.h>
  static pthread_mutex_t A, B;

  void *printmsg1(void *msg) {
      pthread_mutex_lock(&A);  // Execution step 1
      pthread_mutex_lock(&B);  // Execution step 3
      printf("printmsg1\n");
      pthread_mutex_unlock(&B);
      pthread_mutex_unlock(&A);
      return 0;
  }

  void *printmsg2(void *msg) {
      pthread_mutex_lock(&B);  // Execution step 2
      pthread_mutex_lock(&A);
      printf("printmsg2\n");
      pthread_mutex_unlock(&A);
      pthread_mutex_unlock(&B);
      return 0;
  }

  int main(int argc, char**argv) {
      pthread_t pt1, pt2;
      pthread_mutex_init(&A, NULL);
      pthread_mutex_init(&B, NULL);
      pthread_create(&pt1,0, printmsg1, NULL);
      pthread_create(&pt2,0, printmsg2, NULL);
      pthread_join(pt1,0);
      pthread_join(pt2,0);
      pthread_mutex_destroy(&A);
      pthread_mutex_destroy(&B);
      return 0;
  }

Klocwork は 6 行目で、グローバルなミューテックス (相互排除) 'A' と'B' を使用する 2 つのスレッド間でのデッドロックの可能性をレポートします。メインルーチンは、関数 'printmsg1' と関数 'printmsg2' で定義されている 2 つのスレッドを作成し、開始します。1 番目のスレッドは、ミューテックス 'A' のロックを維持したまま、7 行目でミューテックス 'B' のロックを獲得するために待機します。2 番目のスレッドは、ミューテックス 'B' のロックを維持したまま、16 行目でミューテックス 'A' のロックを獲得するために待機します。2 つのスレッド間での、このロックの循環チェーンはデッドロックの原因になります。

修正コード例 1

コピー
  #include <stdio.h>
  #include <pthread.h>
  static pthread_mutex_t A, B;

  void *printmsg1(void *msg) {
      pthread_mutex_lock(&A);
      pthread_mutex_lock(&B);
      printf("printmsg1\n");
      pthread_mutex_unlock(&B);
      pthread_mutex_unlock(&A);
      return 0;
  }

  void *printmsg2(void *msg) {
      pthread_mutex_lock(&A);
      pthread_mutex_lock(&B);
      printf("printmsg2\n");
      pthread_mutex_unlock(&B);
      pthread_mutex_unlock(&A);
      return 0;
  }

  int main(int argc, char**argv) {
      pthread_t pt1, pt2;
      pthread_mutex_init(&A, NULL);
      pthread_mutex_init(&B, NULL);
      pthread_create(&pt1,0, printmsg1, NULL);
      pthread_create(&pt2,0, printmsg2, NULL);
      pthread_join(pt1,0);
      pthread_join(pt2,0);
      pthread_mutex_destroy(&A);
      pthread_mutex_destroy(&B);
      return 0;
  }

各プロセスでの同期化に複数のミューテックスを使用する場合、ミューテックスは同じ順番でロックする必要があります。修正したコード例では、15、16、18、および 19 行で、ロックとロック解除の順番が 'printmsg2' と'printmsg1' とで同じになるように変更され、これによって循環チェーンが絶対に発生しないようになっています。

脆弱コード例 2

コピー
  #include <stdio.h>
  #include <pthread.h>
  static pthread_mutex_t A, B;
   static pthread_cond_t cond;
  static int count;
  
  void *f1(void *msg) {
      pthread_mutex_lock(&A);  
      pthread_mutex_lock(&B);
      while (count) {
          pthread_cond_wait(&cond, &B);
      }  
      pthread_mutex_unlock(&B);
      pthread_mutex_unlock(&A);
      return 0;
  }   
  void *f2(void *msg) {
      pthread_mutex_lock(&A);
      pthread_mutex_lock(&B);
      count--;
      pthread_cond_broadcast(&cond);
      pthread_mutex_unlock(&B);
      pthread_mutex_unlock(&A);
      return 0;
  }   
       
  int main(int argc, char**argv) {
      pthread_t pt1, pt2;
      pthread_mutex_init(&A, NULL);
      pthread_mutex_init(&B, NULL);
      pthread_cond_init(&cond, NULL);
      count = 1;
      pthread_create(&pt1,0, f1, NULL);
      pthread_create(&pt2,0, f2, NULL);
  
      pthread_join(pt1, NULL);
      pthread_join(pt2, NULL);
      pthread_mutex_destroy(&A);
      pthread_mutex_destroy(&B);
      pthread_cond_destroy(&cond);
      return 0;
  }

Klocwork は 11 行目で、グローバルなミューテックス (相互排除) 'A' と条件付き変数 'cond' を使用する 2 つのスレッド間でのデッドロックの可能性をレポートします。1 番目のスレッドは、ミューテックス 'A' のロックを維持したまま、11 行目で条件付き変数 'cond' を無期限に待機する可能性があります。2 番目のスレッドは、グローバル変数 'count' を変更して条件付き変数 'cond' に信号を送るために、18 行目でミューテックス 'A' のロックを獲得するために待機します。

修正コード例 2

コピー
  #include <stdio.h>
  #include <pthread.h>
  static pthread_mutex_t A, B;
  static pthread_cond_t cond;
  static int count;
  
  void *f1(void *msg) {
      pthread_mutex_lock(&B);
      while (count) {
          pthread_cond_wait(&cond, &B);
      }  
      pthread_mutex_unlock(&B);
      return 0;
  }
  void *f2(void *msg) {
      pthread_mutex_lock(&A);
      pthread_mutex_lock(&B);
      count--;
      pthread_cond_broadcast(&cond);
      pthread_mutex_unlock(&B);
      pthread_mutex_unlock(&A);
      return 0;
  }
  
  int main(int argc, char**argv) {
      pthread_t pt1, pt2;
      pthread_mutex_init(&A, NULL);
      pthread_mutex_init(&B, NULL);
      pthread_cond_init(&cond, NULL);
      count = 1;
      pthread_create(&pt1,0, f1, NULL);
      pthread_create(&pt2,0, f2, NULL);
  
      pthread_join(pt1, NULL);
      pthread_join(pt2, NULL);
      pthread_mutex_destroy(&A);
      pthread_mutex_destroy(&B);
      pthread_cond_destroy(&cond);
      return 0;
  }

条件付き待機関数の前のロックのセットには、同じ関数でロック解除される、必要なミューテックスのみを含む必要があります。修正したコード例では、待機関数 'pthread_cond_wait' の前の、必要のないミューテックス 'A' のロックが削除されました。ミューテックス 'A' のロックの削除により、10 行目のデッドロックレポートの可能性がなくなります。

関連チェッカー

セキュリティトレーニング

Secure Code Warrior が提供しているアプリケーションセキュリティトレーニング教材。

拡張機能

このチェッカーは機能を拡張できます。関連するナレッジベースレコードには以下の種類があります。

詳細については、C/C++ 解析のチューニングを参照してください。