SIG31-C. シグナルハンドラ内で共有オブジェクトにアクセスしない
シグナルハンドラで共有オブジェクトにアクセスまたは変更すると、競合状態が発生し、データが矛盾した状態のままになる可能性がある。このルールの例外として、ロックフリーの不可分オブジェクトまたは volatile sig_atomic_t
型変数への読み書きは行ってもよい。volatile
宣言が必要な理由は「DCL34-C. キャッシュできないデータにはvolatile を使う」で解説している。シグナルハンドラから別のタイプのオブジェクトにアクセスするプログラムの動作は未定義であることに注意する必要がある(C 標準の附属書 J 「未定義の動作」の 131 を参照すること)。
sig_atomic_t
型は、非同期割り込みが存在する場合でも、1つの不可分な実体としてアクセスできるオブジェクトの整数型である。sig_atomic_t
型は処理系定義であるが、SIG_ATOMIC_MIN
から SIG_ATOMIC_MAX
の範囲の整数値を安全に格納できることは保証されている。また、sig_atomic_t
が符号付き整数型である場合、SIG_ATOMIC_MIN
は −127 以下でなければならず、SIG_ATOMIC_MAX
は 127 以上でなければならない。それ以外の場合、SIG_ATOMIC_MIN
は 0
でなければならず、SIG_ATOMIC_MAX
は 255 以上でなければならない。マクロ SIG_ATOMIC_MIN
および SIG_ATOMIC_MAX
はヘッダ <stdint.h>
で定義される。
C99 Rationale [ISO/IEC 2003] によると、既定のライブラリ関数群を呼び出す以外にできることとして、以下のように説明している。
C89標準化委員会は次のような結論を下した。規格厳密合致プログラム(strictly conforming program)がシグナルハンドラ内で唯一できることは、割り込みなしで書き込める
volatile static
変数へ値を代入し、ただちに戻ることである。
しかし、この問題は2008年4月に開催されたISO/IEC WG14 の会合で議論され、既存の処理系で volatile static
変数から値を読み取ることがエラーになる処理系は存在しないということで意見が一致した。同委員会の当初の意図としても、volatile sig_atomic_t
変数への読み書きはどちらも規格に厳密に合致するということであった。
シグナルハンドラは、abort()
などごく一部の関数は呼び出すことができる(シグナルハンドラ内から安全に呼び出せる関数の詳細については「SIG30-C. シグナルハンドラ内では非同期安全な関数のみを呼び出す」を参照のこと)。
違反コード
以下のコード例では、err_msg
を更新し SIGINT
シグナルが送信されたことを示している。メモリ割り当てが完了する前に SIGINT
が生成された場合、未定義の動作が発生する。
#include <signal.h> #include <stdlib.h> #include <string.h> char *err_msg; enum { MAX_MSG_SIZE = 24 }; void handler(int signum) { strcpy(err_msg, "SIGINT encountered."); } int main(void) { signal(SIGINT, handler); err_msg = (char *)malloc(MAX_MSG_SIZE); if (err_msg == NULL) { /* エラー条件の処理 */ } strcpy(err_msg, "No errors yet."); /* main コードのループ */ return 0; }
適合コード
シグナルハンドラは、volatile sig_atomic_t
型変数の値の取得や代入を行い、ただちに戻るべきである。
#include <signal.h> #include <stdlib.h> #include <string.h> enum { MAX_MSG_SIZE = 24 }; volatile sig_atomic_t e_flag = 0; void handler(int signum) { e_flag = 1; } int main(void) { char *err_msg = (char *)malloc(MAX_MSG_SIZE); if (err_msg == NULL) { /* エラー条件の処理 */ } signal(SIGINT, handler); strcpy(err_msg, "No errors yet."); /* main コードのループ */ if (e_flag) { strcpy(err_msg, "SIGINT received."); } return 0; }
違反コード (volatile
での誤った型の指定)
以下のコード例では、volatile
を、シグナルハンドラ内でアクセスされる静的な記憶域期間を持つオブジェクトとして宣言している。しかし、オブジェクトの型が sig_atomic_t
でないため、プログラムの動作は未定義となる。また、プログラムの動作が未定義になるのは、SIGFPE
シグナルのハンドラが返されるためでもある。C 標準の附属書 J 「未定義の動作」の 129 を参照すること。
#include <signal.h> extern double compute_value(); static volatile double value; /* バグ: sig_atomic_t が宣言されていない */ void sigfpe_handler(int signum) { if (0.0 == value) /* バグ: sig_atomic_t 以外のオブジェクトへのアクセス */ value = 1.0; /* バグ: SIGFPE ハンドラが返される */ } int main(void) { signal(SIGFPE, sigfpe_handler); value = compute_value(); return 0; }
リスク評価
シグナルハンドラで共有オブジェクトにアクセスしたり変更を行うと、データへのアクセスが矛盾した状況を引き起こす可能性がある。Michal Zalewskiの論文 "Delivering Signals for Fun and Profit" には、本ルールやその他のシグナルハンドリングに関するルールの違反が原因で生じる脆弱性が例示されている [Zalewski 2001]。
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
SIG31-C |
高 |
高 |
高 |
P9 |
L2 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
LDRA tool suite |
V. 8.5.4 |
87 D |
実装済み |
Compass/ROSE |
|
|
単一ファイルのプログラムの場合このルールへの違反を検出できる。 |
関連するガイドライン
CERT C++ Secure Coding Standard | SIG31-CPP. Do not access shared objects in signal handlers |
ISO/IEC TS 17961 (ドラフト) | Accessing shared objects in signal handlers [accsig] |
MITRE CWE | CWE-662, Insufficient synchronization |
参考資料
[Dowd 2006] | Chapter 13, "Synchronization and State" |
[ISO/IEC 2003] | "Signals and Interrupts" |
[Open Group 2004] | longjmp |
[OpenBSD] | signal() Man Page |
[Zalewski 2001] | "Delivering Signals for Fun and Profit" |
翻訳元
これは以下のページを翻訳したものです。
SIG31-C. Do not access shared objects in signal handlers (revision 75)