Main Content

CWE Rule 663

Use of a Non-reentrant Function in a Concurrent Context

Since R2024a

Description

Rule Description

The product calls a non-reentrant function in a concurrent context in which a competing code sequence (e.g. thread or signal handler) may have an opportunity to call the same function or otherwise influence its state.

Polyspace Implementation

The rule checker checks for these issues:

  • Function called from signal handler not asynchronous-safe

  • Function called from signal handler not asynchronous-safe (strict ISO C)

  • Unsafe standard encryption function

  • Unsafe standard function

Examples

expand all

Issue

This issue occurs when a signal handler calls a function that is not asynchronous-safe according to the POSIX standard. An asynchronous-safe function can be interrupted at any point in its execution, then called again without causing an inconsistent state. It can also correctly handle global data that might be in an inconsistent state.

If a signal handler calls another function that calls an asynchronous-unsafe function, the defect appears on the function call in the signal handler. The defect traceback shows the full path from the signal handler to the asynchronous-unsafe function.

Risk

When a signal handler is invoked, the execution of the program is interrupted. After the handler is finished, program execution resumes at the point of interruption. If a function is executing at the time of the interruption, calling it from within the signal handler is undefined behavior, unless it is asynchronous-safe.

Fix

The POSIX standard defines these functions as asynchronous-safe. You can call these functions from a signal handler.

_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() 

Functions not in the previous table are not asynchronous-safe, and should not be called from a signal hander.

Example — Call to printf() Inside Signal 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_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;
}
        
      

In this example, sig_handler calls printf() when catching a signal. If the handler catches another signal while printf() is executing, the behavior of the program is undefined.

Correction — Set Flag Only in Signal Handler

Use your signal handler to set only the value of a flag. e_flag is of type volatile sig_atomic_t. sig_handler can safely access it asynchronously.

#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;
} 
Issue

This issue occurs when a signal handler calls a function that is not asynchronous-safe according to the C standard. An asynchronous-safe function can be interrupted at any point in its execution, then called again without causing an inconsistent state. It can also correctly handle global data that might be in an inconsistent state.

The C standard defines a stricter subset of functions as asynchronous-safe compared to the set of functions that are asynchronous-safe according to the POSIX standard. Function called from signal handler not asynchronous-safe (strict ISO C) reports a defect when a signal handler calls any function that is not part of that subset, even if the function is asynchronous-safe according to the POSIX standard.

To check for calls to functions that are not asynchronous-safe according to the POSIX standard, enable checker Function called from signal handler not asynchronous-safe.

If a signal handler calls another function that calls an asynchronous-unsafe function, the defect appears on the function call in the signal handler. The defect traceback shows the full path from the signal handler to the asynchronous-unsafe function.

Risk

When a signal handler is invoked, the execution of the program is interrupted. After the handler is finished, program execution resumes at the point of interruption. If a function is executing at the time of the interruption, calling it from within the signal handler is undefined behavior, unless it is asynchronous-safe.

Fix

The C standard defines the following functions as asynchronous-safe. You can call these functions from a signal handler:

  • abort()

  • _Exit()

  • quick_exit()

  • signal()

Example — Call to raise() Inside Signal Handler
#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;
}
        
      

In this example, sig_handler calls raise() when catching a signal. If the handler catches another signal while raise() is executing, the behavior of the program is undefined.

Correction — Remove Call to raise() in Signal Handler

According to the C standard, the only functions that you can safely call from a signal handler are abort(), _Exit(), quick_exit(), and 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;
} 
Issue

This issue occurs when a standard encryption function uses a broken or weak cryptographic algorithm. For example, crypt is not reentrant and is based on the risky Data Encryption Standard (DES).

Risk

The use of a broken, weak, or nonstandard algorithm can expose sensitive information to an attacker. A determined hacker can access the protected data using various techniques.

If the weak function is nonreentrant, when you use the function in concurrent programs, there is an additional race condition risk.

Fix

Avoid functions that use these encryption algorithms. Instead, use a reentrant function that uses a stronger encryption algorithm.

Note

Some implementations of crypt support additional, possibly more secure, encryption algorithms.

Example — Decrypting Password Using crypt
#define _GNU_SOURCE
#include <pwd.h>
#include <string.h>
#include <crypt.h>

volatile int rd = 1;

const char *salt = NULL;
struct crypt_data input, output;

int verif_pwd(const char *pwd, const char *cipher_pwd, int safe)
{
    int r = 0;
    char *decrypted_pwd = NULL;
    
    switch(safe)
    {
      case 1: 
        decrypted_pwd = crypt_r(pwd, cipher_pwd, &output);
        break;
        
      case 2: 
        decrypted_pwd = crypt_r(pwd, cipher_pwd, &output);
        break;
        
      default:
        decrypted_pwd = crypt(pwd, cipher_pwd);  //Noncompliant
        break;
    }
    
    r = (strcmp(cipher_pwd, decrypted_pwd) == 0); 
    
    return r;
}

In this example, crypt_r and crypt decrypt a password. However, crypt is nonreentrant and uses the unsafe Data Encryption Standard algorithm.

Correction — Use crypt_r

One possible correction is to replace crypt with crypt_r.

#define _GNU_SOURCE
#include <pwd.h>
#include <string.h>
#include <crypt.h>

volatile int rd = 1;

const char *salt = NULL;
struct crypt_data input, output;

int verif_pwd(const char *pwd, const char *cipher_pwd, int safe)
{
    int r = 0;
    char *decrypted_pwd = NULL;
    
    switch(safe)
    {
      case 1: 
        decrypted_pwd = crypt_r(pwd, cipher_pwd, &output);
        break;
        
      case 2: 
        decrypted_pwd = crypt_r(pwd, cipher_pwd, &output);
        break;
        
      default:
        decrypted_pwd = crypt_r(pwd, cipher_pwd, &output);  
        break;
    }
    
    r = (strcmp(cipher_pwd, decrypted_pwd) == 0);
    
    return r;
}
Issue

This issue occurs when you use standard functions that are unsafe and must not be used for security-related programming. Functions can be unsafe for many reasons. Some functions are unsafe because they are nonreentrant. Other functions change behavior depending on the target or platform, making some implementations unsafe.

Risk

Some unsafe functions are not reentrant, meaning that the contents of the function are not locked during a call. So, an attacker can change the values midstream.

getlogin specifically can be unsafe depending on the implementation. Some implementations of getlogin return only the first eight characters of a log-in name. An attacker can use a different login with the same first eight characters to gain entry and manipulate the program.

Fix

Avoid unsafe functions for security-related purposes. If you cannot avoid unsafe functions, use a safer version of the function instead. For getlogin, use getlogin_r.

Example — Using getlogin
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <string.h>
#include <stdlib.h>


volatile int rd = 1;

int login_name_check(char *user)
{
    int r = -2;
    char *name = getlogin(); //Noncompliant
    if (name != NULL)
    {
        if (strcmp(name, user) == 0)
        {
            r = 0;
        }
        else
            r = -1;
    }

    return r;
}

This example uses getlogin to compare the user name of the current user to the given user name. However, getlogin can return something other than the current user name because a parallel process can change the string.

Correction — Use getlogin_r

One possible correction is to use getlogin_r instead of getlogin. getlogin_r is reentrant, so you can trust the result.

#define _POSIX_C_SOURCE 199506L // use of getlogin_r
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <string.h>
#include <stdlib.h>


volatile int rd = 1;

enum {  NAME_MAX_SIZE=64  };

int login_name_check(char *user)
{
    int r;
    char name[NAME_MAX_SIZE];
    
    if (getlogin_r(name, sizeof(name)) == 0) 
    {
        if ((strlen(user) < sizeof(name)) && 
                     (strncmp(name, user, strlen(user)) == 0))
        {
            r = 0;
        }
        else
            r = -1;
    }
    else
        r = -2;
    return r;
}

Check Information

Category: Concurrency Issues

Version History

Introduced in R2024a