ERR32-C. errno の未規定の値を参照しない
C言語仕様[ISO/IEC 9899:2011]には、以下の場合のプログラムの動作が定義されていない。
abortまたはraise関数の呼び出しの結果以外でシグナルの発生後にerrnoの値を参照し、対応するシグナルハンドラがsignal関数の呼び出しからのSIG_ERRの返り値を取得した場合
(附属書J「未定義の動作」の133を参照。)
シグナルハンドラは signal() を呼び出してもよく、呼び出しが失敗した場合 signal() は SIG_ERR を返し、errno に正の値をセットする。しかし、シグナルを発生させたイベントが外部的なものであった場合(abort() や raise() を呼出しの結果以外)、シグナルハンドラが呼び出せる関数は、_Exit() または abort() あるいは、現在処理中のシグナルに対する signal() 呼び出しであり、signal() が失敗した場合、errno の値は不定となる。
このルールは「SIG31-C. シグナルハンドラ内で共有オブジェクトにアクセスしない」の特殊ケースである。errno が参照するオブジェクトは静的な記憶域期間のオブジェクトであって volatile sig_atomic_t 型ではない。そのため、errno をセットする必要のあるアクションを行うと、通常、未定義の動作を引き起こす。C言語仕様では、このようなケースの errno に対して特別な例外を設けており、誤りが許容される唯一の状況は errno が不定の値を取ることであると規定されている。これにより、無制限の未定義の動作を引き起こすというリスクを冒さずにシグナルハンドラから signal() を呼び出すことが可能になる。ただし、シグナルハンドラおよびハンドラが戻ったあとに実行されるすべてのコードは、errno の値に意味があると思って参照してはならない。
違反コード
既定の動作への変更要求が処理できる場合、signal() 関数は、指定されたシグナルに対して最後に成功した signal() 関数のハンドラの値を返す。そうでない場合、SIG_ERR の値が返し、正の値を errno に格納する。
#include <signal.h>
#include <stdlib.h>
#include <string.h>
typedef void (*pfv)(int);
void handler(int signum) {
pfv old_handler = signal(signum, SIG_DFL);
if (old_handler == SIG_ERR) {
perror("SIGINT handler"); /* 未定義の動作 */
/* エラー条件の処理 */
}
}
int main(void) {
pfv old_handler = signal(SIGINT, handler);
if (old_handler == SIG_ERR) {
perror("SIGINT handler");
/* エラー条件の処理 */
}
/* main コードのループ */
return 0;
}
handler() が perror() を呼び出しているのは「SIG30-C. シグナルハンドラ内では非同期安全な関数のみを呼び出す」にも違反している。
適合コード
以下の適合コードでは、signal() 呼び出しが失敗した場合 errno を参照せず、シグナル処理ルーチンから戻らない。
#include <signal.h>
#include <stdlib.h>
#include <string.h>
typedef void (*pfv)(int);
void handler(int signum) {
pfv old_handler = signal(signum, SIG_DFL);
if (old_handler == SIG_ERR) {
abort();
}
}
int main(void) {
pfv old_handler = signal(SIGINT, handler);
if (old_handler == SIG_ERR) {
perror("SIGINT handler");
/* エラー条件の処理 */
}
/* main コードのループ */
return 0;
}
違反コード (POSIX)
Cと比べPOSIXは、アプリケーションがシグナルハンドラ内で実行できる内容に制限が少ない。POSIXには、呼び出し可能な非同期安全な関数の一覧がある(「SIG30-C. シグナルハンドラ内では非同期安全な関数のみを呼び出す」を参照)。これらの多くの関数ではエラー時に errno がセットされ、これに基づいて、失敗した関数の呼び出しとそれに続く errno の検査の間でシグナルハンドラを実行できる。その結果、検査された値は関数がセットした値ではなく、シグナルハンドラ内での関数呼び出しによりセットされた値となることがある。POSIXアプリケーションでは、以下のようにこの問題を回避することができる。すなわち、errno を変更する可能性のあるコードを含むシグナル処理ルーチンでは、毎回 errno の値を開始時に保存し、戻る前に復元することを保証する。
ルールに違反した以下のコード例では、シグナルハンドラは errno の値を変更している。そのため、シグナルハンドラが、失敗した関数呼び出しとその後の errno の検査の間に実行された場合、誤ったエラー処理を引き起こす可能性がある。
#include <stddef.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
void reaper(int signum) {
errno = 0;
for (;;) {
int rc = waitpid(-1, NULL, WNOHANG);
if ( (0 == rc) || (-1 == rc && EINTR != errno) )
break;
}
if (ECHILD != errno) {
/* エラー処理 */
}
}
int main(void) {
struct sigaction act;
act.sa_handler = reaper;
act.sa_flags = 0;
if (sigemptyset(&act.sa_mask) != 0) {
/* エラー処理 */
}
if (sigaction(SIGCHLD, &act, NULL) != 0) {
/* エラー処理 */
}
/* ... */
return 0;
}
適合コード (POSIX)
以下の解決法では、シグナルハンドラで errno の値を保存し、復元している。
#include <stddef.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
void reaper(int signum) {
int save_errno = errno;
errno = 0;
for (;;) {
int rc = waitpid(-1, NULL, WNOHANG);
if ( (0 == rc) || (-1 == rc && EINTR != errno) )
break;
}
if (ECHILD != errno) {
/* エラー処理 */
}
errno = save_errno;
}
int main(void) {
struct sigaction act;
act.sa_handler = reaper;
act.sa_flags = 0;
if (sigemptyset(&act.sa_mask) != 0) {
/* エラー処理 */
}
if (sigaction(SIGCHLD, &act, NULL) != 0) {
/* エラー処理 */
}
/* ... */
return 0;
}
リスク評価
不定の値を参照すると、未定義の動作が発生する可能性がある。
|
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
|---|---|---|---|---|---|
|
ERR32-C |
低 |
低 |
低 |
P3 |
L3 |
自動検出(最新の情報はこちら)
|
ツール |
バージョン |
チェッカー |
説明 |
|---|---|---|---|
|
Compass/ROSE |
|
|
自身で |
関連するガイドライン
| CERT C++ Secure Coding Standard | ERR32-CPP. Do not rely on indeterminate values of errno |
参考資料
| [ISO/IEC 9899:2011] | Section 7.14.1.1, "The signal Function" |
翻訳元
これは以下のページを翻訳したものです。
ERR32-C. Do not rely on indeterminate values of errno (revision 52)



