メインコンテンツ

信号ハンドラーでの errno の不適切な使用

信号ハンドラーで errno を設定する関数を呼び出した後に errno を読み取る

説明

この欠陥は、信号ハンドラーで以下のいずれかの関数が呼び出された場合に発生します。

  • signal: 信号ハンドラーで関数 signal を呼び出し、次に errno の値を読み取ります。

    たとえば、信号ハンドラー関数 handlersignal を呼び出し、次に errno を読み取る perror を呼び出します。

    typedef void (*pfv)(int);
    
    void handler(int signum) {
      pfv old_handler = signal(signum, SIG_DFL);
      if (old_handler == SIG_ERR) {
        perror("SIGINT handler"); 
      }
    }

  • errno を設定する POSIX® 関数: 信号ハンドラーで errno を設定する POSIX 関数を呼び出しても、その信号ハンドラーから返されたときに errno は復元されません。

    たとえば、信号ハンドラー関数 handler で、errno を変更する waitpid を呼び出しても、返される前に errno は復元されません。

    #include <stddef.h>
    #include <errno.h>
    #include <sys/wait.h>
    
    void handler(int signum) {
      int rc = waitpid(-1, NULL, WNOHANG);
      if (ECHILD != errno) {
      }
    }

リスク

チェッカーによってフラグが設定される各ケースでは、errno の不確定の値に依存するリスクがあります。

  • signal:信号ハンドラーでの signal の呼び出しに失敗した場合、errno の値が不確定になります (C11 規格、節 7.14.1.1).errno の特定の値に依存すると、予期しない結果になる可能性があります。

  • errno を設定する POSIX 関数: errno を設定する関数は失敗時に errno を設定します。信号ハンドラーが呼び出されて、その信号ハンドラー自体により errno を設定する関数が呼び出された後に errno を読み取る場合、予期しない結果になる可能性があります。

修正方法

errno の不確定の値に依存するリスクがある状況を回避します。

  • signal: 信号ハンドラーで関数 signal を呼び出した後に、errno を読み取ったり、errno を読み取る関数を使用したりしないようにします。

  • errno を設定する POSIX 関数: 信号ハンドラーで errno を設定する関数を呼び出す前に、errno を一時変数に保存します。信号ハンドラーから返される前にこの変数から errno を復元します。

すべて展開する

#include <signal.h>
#include <stdlib.h>
#include <stdio.h>

#define fatal_error() abort()

void handler(int signum) {
    if (signal(signum, SIG_DFL) == SIG_ERR) {
        perror("SIGINT handler");
    }
}

int func(void) {
    if (signal(SIGINT, handler) == SIG_ERR) {
        /* Handle error */
        fatal_error();
    }
    /* Program code */
    if (raise(SIGINT) != 0) {
        /* Handle error */
        fatal_error();
    }
    return 0;
}

この例では、SIGINT 信号を処理するために関数 handler が呼び出されます。handler の本体では、関数 signal が呼び出されます。この呼び出し後には errno の値が不確定になります。perrorerrno の値に依存するため、関数 perror が呼び出されるとチェッカーによって欠陥が報告されます。

修正 — signal 呼び出し後の errno の読み取りを回避

1 つの修正方法として、信号ハンドラーで関数 signal を呼び出した後に errno を読み取らないようにします。修正した以下のコードでは、関数 perror ではなく、fatal_error マクロを介して関数 abort を呼び出します。

#include <signal.h>
#include <stdlib.h>
#include <stdio.h>

#define fatal_error() abort()

void handler(int signum) {
    if (signal(signum, SIG_DFL) == SIG_ERR) {
        fatal_error();
    }
} 

int func(void) {
    if (signal(SIGINT, handler) == SIG_ERR) {
        /* Handle error */
        fatal_error();
    }
    /* Program code */
    if (raise(SIGINT) != 0) {
        /* Handle error */
        fatal_error();
    }
    return 0;
}

結果情報

グループ: プログラミング
言語: C | C++
既定値: 手書きコードはオン、生成コードはオフ
コマンド ライン構文: SIG_HANDLER_ERRNO_MISUSE
影響度: Medium

バージョン履歴

R2018a で導入