メインコンテンツ

CWE Rule 364

Signal Handler Race Condition

R2023a 以降

説明

ルールの説明

The software uses a signal handler that introduces a race condition.

Polyspace 実装

ルール チェッカーは以下の問題をチェックします。

  • 非同期安全ではない信号ハンドラーから呼び出された関数

  • 非同期安全ではない信号ハンドラーから呼び出された関数 (厳密な ISO C)

  • 信号ハンドラー内でのデータ アクセスの共有

すべて展開する

問題

この問題は、POSIX 標準によると非同期安全ではない関数を、信号ハンドラーが呼び出した場合に発生します。非同期安全な関数は、実行中のどの時点でも割り込んで再度呼び出すことができ、不整合な状態を発生させません。また、不整合な状態の可能性があるグローバル データを正しく処理できます。

信号ハンドラーで、非同期安全ではない関数を呼び出す別の関数を呼び出す場合、その信号ハンドラーの関数呼び出しに欠陥が表示されます。欠陥のトレースバックには、信号ハンドラーから非同期安全ではない関数までの絶対パスが表示されます。

リスク

信号ハンドラーが呼び出されたときに、プログラムの実行が割り込まれます。ハンドラーが終了した後、割り込まれた箇所からプログラムの実行が再開します。関数が非同期安全ではない限り、割り込み時に関数が実行中の場合、信号ハンドラーからのその関数の呼び出しは未定義の動作です。

修正方法

POSIX 規格では、次の関数を非同期安全として定義しています。信号ハンドラーから次の関数を呼び出すことができます。

_exit()getpgrp()setsockopt()
_Exit()getpid()setuid()
abort()getppid()shutdown()
accept()getsockname()sigaction()
access()getsockopt()sigaddset()
aio_error()getuid()sigdelset()
aio_return()kill()sigemptyset()
aio_suspend()link()sigfillset()
alarm()linkat()sigismember()
bind()listen()signal()
cfgetispeed()lseek()sigpause()
cfgetospeed()lstat()sigpending()
cfsetispeed()mkdir()sigprocmask()
cfsetospeed()mkdirat()sigqueue()
chdir()mkfifo()sigset()
chmod()mkfifoat()sigsuspend()
chown()mknod()sleep()
clock_gettime()mknodat()sockatmark()
close()open()socket()
connect()openat()socketpair()
creat()pathconf()stat()
dup()pause()symlink()
dup2()pipe()symlinkat()
execl()poll()sysconf()
execle()posix_trace_event()tcdrain()
execv()pselect()tcflow()
execve()pthread_kill()tcflush()
faccessat()pthread_self()tcgetattr()
fchdir()pthread_sigmask()tcgetpgrp()
fchmod()quick_exit()tcsendbreak()
fchmodat()raise()tcsetattr()
fchown()read()tcsetpgrp()
fchownat()readlink()time()
fcntl()readlinkat()timer_getoverrun()
fdatasync()recv()timer_gettime()
fexecve()recvfrom()timer_settime()
fork()recvmsg()times()
fpathconf()rename()umask()
fstat()renameat()uname()
fstatat()rmdir()unlink()
fsync()select()unlinkat()
ftruncate()sem_post()utime()
futimens()send()utimensat()
getegid()sendmsg()utimes()
geteuid()sendto()wait()
getgid()setgid()waitpid()
getgroups()setpgid()write()
getpeername()setsid() 

上の表にない関数は非同期安全ではありません。信号ハンドラーから呼び出さないようにします。

例 — 信号ハンドラー内の printf() の呼び出し
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <setjmp.h>
#include <syslog.h>
#include <unistd.h>

#define SIZE20 20

extern volatile sig_atomic_t e_flag;

void display_info(const char *info)
{
    if (info)
    {
        (void)fputs(info, stderr);
    }
}

void sig_handler(int signum)
{
    /* Call function printf() that is not
	asynchronous-safe */
	printf("signal %d received.", signum);  //Noncompliant
    e_flag = 1;
}

int main(void)
{
    e_flag = 0;
    if (signal(SIGINT, sig_handler) == SIG_ERR)
    {
        /* Handle error */
    }
    char *info = (char *)calloc(SIZE20, sizeof(char));
    if (info == NULL)
    {
        /* Handle Error */
    }
    while (!e_flag)
    {
        /* Main loop program code */
        display_info(info);
        /* More program code */
    }
    free(info);
    info = NULL;
    return 0;
}
        
      

この例では、信号をキャッチするときに sig_handlerprintf() を呼び出しています。printf() の実行中にハンドラーが別の信号をキャッチする場合、プログラムの動作は未定義です。

修正 — 信号ハンドラーにフラグのみを設定

信号ハンドラーを使用して、フラグの値のみを設定します。e_flag は、volatile sig_atomic_t 型です。sig_handler はこれに非同期で安全にアクセスできます。

#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <setjmp.h>
#include <syslog.h>
#include <unistd.h>

#define SIZE20 20

extern volatile sig_atomic_t e_flag;

void display_info(const char *info)
{
    if (info)
    {
        (void)fputs(info, stderr);
    }
}

void sig_handler1(int signum)
{
    int s0 = signum;
    e_flag = 1;       
}

int func(void)
{
    e_flag = 0;
    if (signal(SIGINT, sig_handler1) == SIG_ERR)
    {
        /* Handle error */
    }
    char *info = (char *)calloc(SIZE20, 1);
    if (info == NULL)
    {
        /* Handle error */
    }
    while (!e_flag)
    {
        /* Main loop program code */
        display_info(info);
        /* More program code */
    }
    free(info);
    info = NULL;
    return 0;
} 
問題

この問題は、C 標準によると非同期安全ではない関数を、信号ハンドラーが呼び出した場合に発生します。非同期安全な関数は、実行中のどの時点でも割り込んで再度呼び出すことができ、不整合な状態を発生させません。また、不整合な状態の可能性があるグローバル データを正しく処理できます。

C 標準では、POSIX 規格に従って非同期安全である関数のセットと比較して、より厳密な関数のサブセットを非同期安全として定義しています。"非同期安全ではない信号ハンドラーから呼び出された関数 (厳密な ISO C)" は、関数が POSIX 規格に従って非同期安全である場合でも、信号ハンドラーがそのサブセットに含まれていない関数を呼び出したときに欠陥を報告します。

POSIX 規格に従って非同期安全ではない関数の呼び出しをチェックするには、チェッカーの "非同期安全ではない信号ハンドラーから呼び出された関数" を有効にします。

信号ハンドラーで、非同期安全ではない関数を呼び出す別の関数を呼び出す場合、その信号ハンドラーの関数呼び出しに欠陥が表示されます。欠陥のトレースバックには、信号ハンドラーから非同期安全ではない関数までの絶対パスが表示されます。

リスク

信号ハンドラーが呼び出されたときに、プログラムの実行が割り込まれます。ハンドラーが終了した後、割り込まれた箇所からプログラムの実行が再開します。関数が非同期安全ではない限り、割り込み時に関数が実行中の場合、信号ハンドラーからのその関数の呼び出しは未定義の動作です。

修正方法

C 標準では、次の関数を非同期安全として定義しています。信号ハンドラーから次の関数を呼び出すことができます。

  • abort()

  • _Exit()

  • quick_exit()

  • signal()

例 — 信号ハンドラー内の raise() の呼び出し
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <setjmp.h>
#include <syslog.h>
#include <unistd.h>

void SIG_ERR_handler(int signum)
{
    int s0 = signum;
    /* SIGTERM specific handling */
}

void sig_handler(int signum)
{
    int s0 = signum;
	/* Call raise() */ 
    if (raise(SIGTERM) != 0) { //Noncompliant
        /* Handle error */
    }
}

int finc(void)
{
    if (signal(SIGTERM, SIG_ERR_handler) == SIG_ERR)
    {
        /* Handle error */
    }
    if (signal(SIGINT, sig_handler) == SIG_ERR)
    {
        /* Handle error */
    }
    /* Program code */
    if (raise(SIGINT) != 0)
    {
        /* Handle error */
    }
    /* More code */
    return 0;
}
        
      

この例では、信号をキャッチするときに sig_handlerraise() を呼び出しています。raise() の実行中にハンドラーが別の信号をキャッチする場合、プログラムの動作は未定義です。

修正 — 信号ハンドラーの raise() の呼び出しを削除

C 標準によると、信号ハンドラーから安全に呼び出すことができる関数は、abort()_Exit()quick_exit()、および signal() のみです。

#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <setjmp.h>
#include <syslog.h>
#include <unistd.h>

void SIG_ERR_handler(int signum)
{
    int s0 = signum;
    /* SIGTERM specific handling */
}
void sig_handler(int signum)
{
    int s0 = signum;
	
  
}

int func(void)
{
    if (signal(SIGTERM, SIG_ERR_handler) == SIG_ERR)
    {
        /* Handle error */
    }
    if (signal(SIGINT, sig_handler) == SIG_ERR)
    {
        /* Handle error */
    }
    /* Program code */
    if (raise(SIGINT) != 0)
    {
        /* Handle error */
    }
    /* More code */
    return 0;
} 
問題

この問題は、信号ハンドラー内で共有オブジェクトへのアクセスまたは変更を行った場合に発生します。

リスク

共有オブジェクトへのアクセスまたは変更を行う信号ハンドラー関数を定義する場合、ハンドラーは信号を受け取ると共有オブジェクトへのアクセスまたは変更を行います。他の関数が既にその共有オブジェクトにアクセス中の場合、その関数が競合状態を発生させ、データが不整合状態になる可能性があります。

修正方法

信号ハンドラー内の共有オブジェクトへのアクセスまたは変更を行うには、オブジェクトがロック制御不要のアトミックであることをチェックするか、オブジェクトが整数の場合は volatile sig_atomic_t として宣言します。

例 — 共有ハンドラー内での int 型変数へのアクセス
#include <signal.h>
#include <stdlib.h>
#include <string.h>

/* declare global variable. */
int e_flag;

void sig_handler(int signum)
{
	/* Signal handler accesses variable that is not
	of type volatile sig_atomic_t. */
    e_flag = signum; //Noncompliant
}

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

この例では、sig_handlerint 型の変数である e_flag にアクセスしています。別の関数からの同時アクセスによって、e_flag が不整合状態になる可能性があります。

修正 — volatile sig_atomic_t 型の変数を宣言

信号ハンドラーから共有変数にアクセスする前に、変数を int 型ではなく volatile sig_atomic_t 型で宣言します。この型の変数には非同期で安全にアクセスできます。

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

/* Declare variable of type volatile sig_atomic_t. */
volatile sig_atomic_t e_flag;
void sig_handler(int signum)
{
	/* Use variable of proper type inside signal handler. */
    e_flag = signum;
    
}

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

チェック情報

カテゴリ: Signal Errors

バージョン履歴

R2023a で導入