JPCERT コーディネーションセンター

SIG34-C. 割り込み可能なシグナルハンドラ内から signal() を呼び出さない

シグナルハンドラの中で、自分自身を同じシグナルのハンドラとして登録すべきでない。非継続型シグナルハンドラを持つプラットフォームでは、このような処理がよく行われている。非継続型シグナルハンドラとは、シグナルが発生したとき、シグナルハンドラの登録がデフォルトに戻されてから、登録されていたハンドラが呼び出されるものである。このような条件で signal() を呼び出すと、競合状態が発生する。「SIG01-C. シグナルハンドラの継続性に関して処理系定義の詳細を理解する」を参照のこと。

シグナルハンドラでの signal() の呼び出しが可能なのは、非同期安全である必要のない場合だけに限定される(つまり、関連するシグナルがすべてマスクされており、割り込まれる可能性がない)。

違反コード

以下のコード例では、シグナルハンドラ handler()signum に割り当てられている。

void handler(int signum) {
  if (signal(signum, handler) == SIG_ERR) {
    /* エラー処理 */
  }
  /* シグナルの処理 */
}
/* ... */
if (signal(SIGUSR1, handler) == SIG_ERR) {
  /* エラー処理 */
}

非継続型シグナルハンドラを持つプラットフォームにおいては、このコードには競合ウィンドウが存在する。競合ウィンドウは、ホスト環境がシグナルハンドラをリセットしたときに開き、シグナルハンドラが signal() を呼び出したときに閉じる。この期間中にプログラムに送信された2番目のシグナルは、デフォルトのシグナル動作を引き起こす。結果として、シグナルハンドラ内から signal() 呼び出しにより当該シグナルの処理に再度シグナルハンドラを登録しようとした意図が達成されない。

継続型シグナルハンドラを持つ環境では、handler() 内で signal() を呼び出すのは冗長である。

適合コード

継続型シグナルハンドラを持つプラットフォームの場合、シグナルハンドラ内からの signal() 関数を呼び出す必要はない。

void handler(int signum) {
  /* シグナルの処理 */
}
/* ... */
if (signal(SIGUSR1, handler) == SIG_ERR) {
  /* エラー処理 */
}
適合コード (POSIX)

POSIX は sigaction() 関数を定義している。この関数は、signal() と同じく、シグナルにハンドラを割り当てると、同時に呼び出し元がその継続性を明示的に指定することができる。このため、非継続型シグナルハンドラを持つプラットフォームでは、sigaction() 関数を使うことで競合状態を取り除ける。

void handler(int signum) {
  /* シグナルの処理 */
}

/* ... */

struct sigaction act;
act.sa_handler = handler;
act.sa_flags = 0;
if (sigemptyset( &act.sa_mask) != 0) {
  /* エラー処理 */
}
if (sigaction(SIGUSR1, &act, NULL) != 0) {
  /* エラー処理 */
}

このコード例ではシグナルハンドラは signal() を呼び出していないが、たとえ呼び出しても安全である。なぜなら、該当するシグナルはマスクされており、ハンドラに対して割り込みは発生しないためである。システムは送信中のシグナルに対してマスクするが、同一ハンドラが複数のシグナル番号に対して登録されている場合には、act.sa_mask でシグナルを明示的にマスクし、そのハンドラが割り込まれないことを保証する必要がある。

POSIX は sigaction() の使用を推奨しており、signal() の使用は推奨していない。残念ながら、sigaction() は C に準拠しておらず、Windows などいくつかのプラットフォームではサポートされていない。

適合コード (Windows)

Windows ではシグナルに継続性のある挙動を安全に実装する方法は存在しない。ソフトウェアの設計がシグナルの継続性を前提としており、かつ設計を変更できないならば、適切なリスク評価を行った上で、このルールから逸脱していることを明記すべきである。

例外

SIG34-EX1:継続型シグナルハンドラを持つ環境では、ハンドラは自分自身のシグナルに対する動作を安全に変更できる。動作の変更には、シグナルを無視する、デフォルト動作にリセットする、別のハンドラに処理させるなどが含まれる。自分自身を同一シグナルに再度割り当てるハンドラも、なにもしないことになるため安全である。そのようなハンドラは、別のハンドラを登録しない限り競合状態の影響を受けない。なぜならば、シグナルが複数回発生してハンドラが呼び出されても自分自身に割り込むだけだからである。

次のコード例は、シグナルハンドラをリセットしてデフォルト動作に戻している。

void handler(int signum) {
#ifndef WINDOWS
  if (signal(signum, SIG_DFL) == SIG_ERR) {
    /* エラー処理 */
  }
#endif
  /* シグナルの処理 */
}
/* ... */
if (signal(SIGUSR1, handler) == SIG_ERR) {
  /* エラー処理 */
}
リスク評価

非継続型シグナルハンドラを持つプラットフォームでは、2 つのシグナルが連続して発生すると競合状態が発生する。シグナルハンドラがデフォルトの動作を変更する前に、デフォルト動作が発生してしまう。

レコメンデーション

深刻度

可能性

修正コスト

優先度

レベル

SIG34-C

P3

L3

自動検出

 

ツール

バージョン

チェッカー

説明

Compass/ROSE     このルールの違反を検出できる。ハンドラーが継続性のあるシステムでは誤検出が発生するかもしれない。
PRQA QA-C 8.1 Warncall -wc signal 部分的に実装済み
関連するガイドライン
CERT C++ Secure Coding Standard SIG34-CPP. Do not call signal() from within interruptible signal handlers
ISO/IEC TS 17961 (ドラフト) Calling signal from interruptible signal handlers [sigcall]
MITRE CWE CWE-479, Unsafe function call from a signal handler
翻訳元

これは以下のページを翻訳したものです。

SIG34-C. Do not call signal() from within interruptible signal handlers (revision 59)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter