SIG33-C. raise() 関数を再帰的に呼び出さない
このガイドラインは廃止され、以下のガイドラインに統合されました。
C99 では raise() 関数を再帰的に呼び出すことは認められていない。C99 のセクション7.14.1.1#4 [ISO/IEC 9899:1999] は以下のように規定している。
シグナルが、abort 関数又は raise 関数の呼出しの結果として発生する場合、シグナル処理ルーチンは、raise 関数を呼び出してはならない。
違反コード
以下のコード例では、handler() 関数を使って SIGINT に対応する処理を実行し、次に SIGUSR1 を発生させ割り込みのログを記録する。しかし、raise() 関数を再帰的に呼び出しており、未定義の動作となる。
#include <signal.h> void log_msg(int signum) { /* async-safe なエラー記録ルーチン */ } void handler(int signum) { /* SIGINT 固有の処理 */ if (raise(SIGUSR1) != 0) { /* エラー処理 */ } } int main(void) { if (signal(SIGUSR1, log_msg) == SIG_ERR) { /* エラー処理 */ } if (signal(SIGINT, handler) == SIG_ERR) { /* エラー処理 */ } /* プログラムコード */ if (raise(SIGINT) != 0) { /* エラー処理 */ } /* さらにコードが続く */ return 0; }
適合コード
以下の解決法では、handler() 内で raise() 関数を呼び出す代わりに、log_msg() を直接呼び出している。
#include <signal.h> void log_msg(int signum) { /* async-safe なエラー記録ルーチン */ } void handler(int signum) { /* SIGINT 固有の処理 */ log_msg(SIGUSR1); } int main(void) { if (signal(SIGUSR1, log_msg) == SIG_ERR) { /* エラー処理 */ } if (signal(SIGINT, handler) == SIG_ERR) { /* エラー処理 */ } /* プログラムコード */ if (raise(SIGINT) != 0) { /* エラー処理 */ } /* さらにコードが続く */ return 0; }
適合コード (POSIX)
POSIX の sigaction() 関数を使ってシグナルハンドラを割り当てていれば、シグナルハンドラは安全に raise() を呼び出すことができる。
POSIX 標準は、シグナルハンドラ内から raise() を呼び出すことに関してはC99と相反した立場をとる。POSIX 標準 [Open Group 04] では、raise() 関数、kill() 関数、pthread_kill() 関数、sigqueue() 関数の呼び出しによりシグナルが発生した場合、signal() を使って登録されたシグナルハンドラが、raise() 関数を呼び出すことを禁止している。しかし、raise() 関数はすべてのシグナルハンドラ内で安全に呼び出せることを要求している(「SIG30-C. シグナルハンドラ内では非同期安全な関数のみを呼び出す」を参照)。結果として、signal() を使って登録されたシグナルハンドラ内で POSIX アプリケーションが raise() を呼び出すことが安全かどうかは明らかでないが、sigaction() を使って登録されたシグナルハンドラ内で raise() を呼び出すことは安全だといえる。
#include <signal.h> void log_msg(int signum) { /* async-safe なエラー記録ルーチン */ } void handler(int signum) { /* SIGINT 固有の処理 */ if (raise(SIGUSR1) != 0) { /* エラー処理 */ } } int main(void) { struct sigaction act; act.sa_flags = 0; if (sigemptyset(&act.sa_mask) != 0) { /* エラー処理 */ } act.sa_handler = log_msg; if (sigaction(SIGUSR1, &act, NULL) != 0) { /* エラー処理 */ } act.sa_handler = handler; if (sigaction(SIGINT, &act, NULL) != 0) { /* エラー処理 */ } /* program code */ if (raise(SIGINT) != 0) { /* エラー処理 */ } /* さらにコードが続く */ return 0; }
POSIX は sigaction() の使用を推奨しており、signal() は推奨していない。残念ながら、sigaction() は C99 で定義されていないため、可搬性のある解決法ではない。
リスク評価
abort() または raise() の呼び出しによりシグナルが発生し、呼び出されたシグナルハンドラの中から raise() 関数が呼び出されると、未定義の動作が発生する。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
SIG33-C | 低 | 低 | 中 | P2 | L3 |
自動検出
Compass/ROSE は単一ファイルのプログラムについてこのルールの違反を検出することができる。
参考情報
- [Dowd 06] Chapter 13, "Synchronization and State"
- [ISO/IEC 9899:1999] Section 7.14.1.1, "The signal function"
- [MITRE 07] CWE ID 479, "Unsafe Function Call from a Signal Handler"
- [Open Group 04]
- [OpenBSD] signal() Man Page
翻訳元
これは以下のページを翻訳したものです。