SIG02-C. 標準的な機能を実装する際はシグナルの使用を避ける
標準的な機能を実装する際はシグナルの使用を避けよ。シグナルハンドラは、可搬性があり安全な方法で動作できる処理の中で使用されるように厳しく制限する。シグナルハンドラの使用は、ログ記録では対応できない異常なイベントに対応するために確保しておく必要がある。
違反コード
以下のコード例は、マルチスレッド環境内で状態の変更を知らせる手段としてシグナルを使用している。
/* スレッド 1 */ int do_work(void) { /* ... */ kill(THR2_PID, SIGUSR1); } /* スレッド 2 */ volatile sig_atomic_t flag; void sigusr1_handler(int signum) { flag = 1; } int wait_and_work(void) { flag = 0; while (!flag) {} /* ... */ }
しかし、このような機能にシグナルを使用すると、結局は可搬性がないか複雑な解決策に至ることが多い。
上記のコードは、あるスレッドでシグナルを使用して 2 番目のスレッドを起動させる例を示している。アーキテクチャが本来備えているスレッドライブラリを使用することで、通常は、より洗練された方法でスレッド間でメッセージを送信することができる。
適合コード (POSIX)
より良い解決法は、条件変数を使用することである。以下のコードは、POSIX pthread
ライブラリ [Open Group 2004] にある条件変数を使用している。
#include <pthread.h> pthread_cond_t cond = PTHREAD_COND_INITIALIZER; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; /* スレッド 1 */ int do_work(void) { int result; /* ... */ if ((result = pthread_mutex_lock(&mut)) != 0) { /* エラー条件の処理 */ } if ((result = pthread_cond_signal(&cond,&mut)) != 0) { /* エラー条件の処理 */ } if ((result = pthread_mutex_unlock(&mut)) != 0) { /* エラー条件の処理 */ } } /* スレッド 2 */ int wait_and_work(void) { if ((result = pthread_mutex_lock(&mut)) != 0) { /* エラー条件の処理 */ } while (/* condition does not hold */) { if ((result = pthread_cond_wait(&cond, &mut)) != 0) { /* エラー条件の処理 */ } /* ... */ } if ((result = pthread_mutex_unlock(&mut)) != 0) { /* エラー条件の処理 */ } /* ... */ }
適合コード (Windows)
以下の適合コードでは、Win32 API [MSDN] の条件変数を使用している。
#include <windows.h> CRITICAL_SECTION CritSection; CONDITION_VARIABLE ConditionVar; /* スレッド 1 */ int do_work(void) { /* ... */ EnterCriticalSection(&CritSection); WakeConditionVariable(&ConditionVar); LeaveCriticalSection(&CritSection); } /* スレッド 2 */ int wait_and_work(void) { EnterCriticalSection(&CritSection); SleepConditionVariableCS(&ConditionVar, &CritSection, INFINITE); LeaveCriticalSection(&CritSection); /* ... */ }
違反コード
以下のコード例は、WU-FTPD v2.4 のシグナル競合の脆弱性 [Greenman 1997] から抜粋したものである。
void dologout(status) { if (logged_in) { (void) seteuid((uid_t)0); logwtmp(ttyline, "", ""); /* ... */ } _exit(status); } static void lostconn(int signo) { if (debug) syslog(LOG_DEBUG, "lost connection"); dologout(-1); } static void myoob(signo) { if (!transflag) return; /* ... */ if (strcmp(cp, "ABOR\r\n") == 0) { tmpline[0] = '\0'; reply(426, "Transfer aborted. Data connection closed."); reply(226, "Abort successful"); longjmp(urgcatch, 1); } /* ... */ } /* ... */ signal(SIGPIPE, lostconn); signal(SIGURG, myoob);
dologout()
の中で権限の昇格が行われた直後に SIGURG
が捕捉された場合、重大な結果を招く攻撃が起きる可能性がある。SIGURG
ハンドラの myoob()
の中で longjmp()
が呼び出されると、実効ユーザ ID 0 でメインの処理ループに実行が戻る。
このコード例は、「SIG30-C. シグナルハンドラ内では非同期安全な関数のみを呼び出す」、「SIG31-C. シグナルハンドラ内で共有オブジェクトにアクセスしない」、および「SIG32-C. シグナルハンドラ内からlongjmp() を呼び出さない」に違反していることに注意。
応急処置としては、dologout()
が SIGURG
によって割り込まれないようにする方法がある。
void dologout(status) { /* * SIGURG を受信した場合に、メインのプログラム * ループに戻って再開するのを回避する */ transflag = 0; if (logged_in) { (void) seteuid((uid_t)0); logwtmp(ttyline, "", ""); /* ... */ } _exit(status); }
より良い解決法は、myoob()
で、volatile sig_atomic_t
型の失敗フラグの値を設定するようにし、メインループ内でこれを定期的に検査することである。
volatile sig_atomic_t xfer_aborted = 0; static void myoob(signo) { /* ... */ if (strcmp(cp, "ABOR\r\n") == 0) { xfer_aborted = 1; } /* ... */ }
ただしこの解決法は、依然として「SIG30-C. シグナルハンドラ内では非同期安全な関数のみを呼び出す」および「SIG31-C. シグナルハンドラ内で共有オブジェクトにアクセスしない」に違反している。
適合コード
ここにはコードを示さないが、適合コードは、シグナルを使用して接続断を知らせるのではなく、堅牢なエラー処理メカニズムを備えるようにシステムを設計することである。(「ERR00-C. エラー処理には一貫性のある方針を採用する」を参照)。
リスク評価
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
SIG02-C |
高 |
中 |
中 |
P12 |
L1 |
関連する脆弱性
標準的な機能を実装するのにシグナルを使用すると、シグナル処理の 1 つ以上の安全なコーディング規則に対する違反が発生することがある。
関連するガイドライン
CERT C++ Secure Coding Standard | SIG02-CPP. Avoid using signals to implement normal functionality |
参考資料
[Dowd 2006] | Chapter 13, "Synchronization and State" |
[Greenman 1997] | |
[MSDN] | Using Condition Variables |
[Open Group 2004] | pthread.h |
翻訳元
これは以下のページを翻訳したものです。
SIG02-C. Avoid using signals to implement normal functionality (revision 61)