メインコンテンツ

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

割り込み関数の呼び出しは未定義のプログラム動作を引き起こす

説明

この欠陥は、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() 

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

すべて展開する

#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); 
    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++
既定値: オフ
コマンド ライン構文: SIG_HANDLER_ASYNC_UNSAFE
影響度: Medium

バージョン履歴

R2017b で導入