SIG00-C. 割り込み不可能なシグナルハンドラによって処理されるシグナルをマスクする
シグナルとは、制御の受け渡しのための仕組みであり、通常はあるイベントが発生したことをプロセスに通知するために使用される。シグナルを受け取ったプロセスは、それに応じてそのイベントに対応できる。C 標準は、C プログラム内でシグナルの送信や処理を行う関数をいくつか規定している。
プロセスは、signal()
関数(仕様は以下の通り)でシグナルハンドラを登録することによってシグナルを処理する。
void (*signal(int sig, void (*func)(int)))(int);
これは以下のコードと概念的には同等である。
typedef void (*sighandler_t)(int signum); extern sighandler_t signal( int signum, sighandler_t handler );
シグナルハンドラは、シグナルの発生によって割り込まれる可能性がある。シグナルハンドラが呼び出されるときに、ハンドラが呼び出される原因になったシグナルがマスクされていない場合、シグナルハンドラは自身の処理に割り込みをかける可能性もある。自身の処理に割り込んでも、ほかのシグナルによって割り込まれても、処理を正常に完了するハンドラは「非同期安全(async-signal-safe)」である。
いくつかのプラットフォームは、シグナルハンドラが処理されている間、シグナルをマスクする機能を備えている。シグナルハンドラが処理されている間シグナルがマスクされていれば、そのシグナルハンドラは割り込まれる可能性がないため、非同期安全である必要はない。ただし、シグナルハンドラが処理されている間シグナルがマスクされていても、そのシグナルハンドラは 非同期安全の安全でない関数の呼び出しは避ける必要がある。呼び出しを行うと、別のシグナルによって割り込まれる(割り込まれている)可能性がある。
非同期安全でないシグナルハンドラが、(自分自身も含め)マスクされていないシグナルによって割り込まれた場合、脆弱性が生じる可能性がある。
違反コード
以下のコード例は、SIGUSR1
と SIGUSR2
を処理する単一のシグナルハンドラを登録している。このシグナルハンドラは、1 つ以上の SIGUSR1
シグナルに続いて SIGUSR2
が送信された場合に、変数 sig2
を 1
に設定する。
#include <signal.h> volatile sig_atomic_t sig1 = 0; volatile sig_atomic_t sig2 = 0; void handler(int signum) { if (signum == SIGUSR1) { sig1 = 1; } else if (sig1) { sig2 = 1; } } int main(void) { if (signal(SIGUSR1, handler) == SIG_ERR) { /* エラー処理 */ } if (signal(SIGUSR2, handler) == SIG_ERR) { /* エラー処理 */ } while (sig2 == 0) { /* 何もしないか、少しの間 CPU を手放す */ } /* ... */ return 0; }
残念ながら、handler()
の実装では競合状態が発生する。SIGUSR1
の処理のために handler()
が呼び出され、同時に SIGUSR2
の処理のために handler()
が呼び出された場合、sig2
は設定されない。
適合コード (POSIX)
POSIX の sigaction()
関数は、C の signal()
関数と同様の方法でシグナルにハンドラを割り当てるが、シグナルマスクを明示的に設定することができる。このため、sigaction()
を使用して、シグナルハンドラが自身に割り込みをかけるのを避けることができる。
#include <signal.h> #include <stdio.h> volatile sig_atomic_t sig1 = 0; volatile sig_atomic_t sig2 = 0; void handler(int signum) { if (signum == SIGUSR1) { sig1 = 1; } else if (sig1) { sig2 = 1; } } int main(void) { struct sigaction act; act.sa_handler = &handler; act.sa_flags = 0; if (sigemptyset(&act.sa_mask) != 0) { /* エラー処理 */ } if (sigaddset(&act.sa_mask, SIGUSR1)) { /* エラー処理 */ } if (sigaddset(&act.sa_mask, SIGUSR2)) { /* エラー処理 */ } if (sigaction(SIGUSR1, &act, NULL) != 0) { /* エラー処理 */ } if (sigaction(SIGUSR2, &act, NULL) != 0) { /* エラー処理 */ } while (sig2 == 0) { /* 何もしないか、少しの間 CPU を手放す */ } /* ... */ return 0; }
POSIX は sigaction()
の使用を推奨しており、signal()
の使用は推奨していない。残念ながら、sigaction()
は C 標準で定義されていないため、可搬性のある解決法ではない。
リスク評価
非同期安全でないシグナルハンドラに割り込みがかけられた場合、さまざまな脆弱性が生じる可能性がある[Zalewski 2001]。
レコメンデーション |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
SIG00-C |
高 |
高 |
高 |
P9 |
L2 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
PRQA QA-C | 8.1 | warncall for signal | 部分的に実装済み |
関連するガイドライン
CERT C++ Secure Coding Standard | SIG00-CPP. Mask signals handled by noninterruptible signal handlers |
MITRE CWE | CWE-662, Insufficient synchronization |
参考資料
[Dowd 2006] | Chapter 13, "Synchronization and State" ("Signal Interruption and Repetition") |
[ISO/IEC 2003] | Section 5.2.3, "Signals and Interrupts" |
[Open Group 2004] | longjmp |
[OpenBSD] | signal() Man Page |
[Zalewski 2001] | "Delivering Signals for Fun and Profit" |
翻訳元
これは以下のページを翻訳したものです。
SIG00-C. Mask signals handled by noninterruptible signal handlers (revision 74)