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 が提供しているアプリケーションセキュリティトレーニング教材。