Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Some functions in the C standard library are not guaranteed to be reentrant with respect to threads. Some functions (such as strtok() and asctime()) return a pointer to the result stored in function-allocated memory on a per-process basis. Other functions (such as rand()) store state information in function-allocated memory on a per-process basis. Multiple threads invoking the same function can cause concurrency problems, which often result in abnormal behavior and can cause more serious vulnerabilities, such as abnormal termination, denial-of-service attack, and data integrity violations.

According to the C Standard [ISO/IEC 9899:2011], the following library functions are not required to avoid data races:

  • rand()
  • getenv()
  • strtok()
  • strerror()
  • asctime()
  • ctime()

Section 2.9.1 of the System Interfaces volume of POSIX.1-2008 has a much longer list of functions that are not required to be thread-safe.

Noncompliant Code Example (POSIX)

Consider a multithreaded application that encounters an error while calling a system function. The strerror() function returns a human-readable error string given an error number. The C Standard, section 7.24.6.2, specifically states that strerror() is not required to avoid data races. Conventionally, it could rely on a static array that maps error numbers to error strings, and that array might be accessible and modifiable by other threads. (This code is specific to POSIX because fopen() is not guaranteed to set errno if an error occurs in C99 or C11.)

Code Block
bgColor#FFCCCC
langc
errno = 0;
FILE* fd = fopen( filename, "r");
if (fd == NULL) {
  char* errmsg = strerror(errno);
  printf("Could not open file because of %s\n", errmsg);
}

Note that this code first sets errno to 0 to comply with ERR30-C. Set errno to zero before calling a library function known to set errno, and check errno only after the function returns a value indicating failure.

Noncompliant Code Example (C99, strerror_r())

This noncompliant code example uses the POSIX strerror_r() function, which has the same functionality as strerror() but guarantees thread safety.

Code Block
bgColor#FFCCCC
langc
errno = 0;
FILE* fd = fopen( filename, "r");
if (fd == NULL) {
  char errmsg[BUFSIZ];
  if (strerror_r(errno, errmsg, BUFSIZ) != 0) {
    /* handle error */
  }
  printf("Could not open file because of %s\n", errmsg);
}

While this code prevents a race window from being exploited within the strerror_r() function itself, the fact that errno is a static variable means there is still a race window between the fopen() call and the beginning of the strerror_r() call, in which another thread could modify errno.

Compliant Solution (C99 mutex) 

This compliant solution adds a mutex to protect the access of errno by multiple threads.

Code Block
bgColor#ccccff
langc
static pthread_mutex_t errno_mutex;
int result;
if ((result = pthread_mutex_lock(&errno_mutex)) == 0) {
  /* Handle error */
}
errno = 0;
FILE* fd = fopen( filename, "r");
if (fd == NULL) {
  char errmsg[BUFSIZ];
  if (strerror_r(errno, errmsg, BUFSIZ) != 0) {
    /* handle error */
  }
  printf("Could not open file because of %s\n", errmsg);
}
if ((result = pthread_mutex_unlock(&errno_mutex)) == 0) {
  /* Handle error */
}

Note that Linux provides two versions of strerror_r(), known as the XSI-compliant version and the GNU-specific version. This compliant solution assumes the XSI-compliant version. You can get the XSI-compliant version if you compile applications in the way POSIX requires (that is, by defining _POSIX_C_SOURCE or _XOPEN_SOURCE appropriately). Check your strerror_r() manual page to see which version(s) are available on your system.

Compliant Solution (C11 strerror_s()

This compliant solution uses the strerror_s() function from Annex K of the C Standard, which has the same functionality as strerror() but guarantees thread-safety. Furthermore, in C11, errno is a thread-local variable, so there is no race condition between when it is initialized and read by strerror_s().

Code Block
bgColor#ccccff
langc
errno = 0;
FILE* fd = fopen( filename, "r");
if (fd == NULL) {
  char errmsg[BUFSIZ];
  if (strerror_s(errno, errmsg, BUFSIZ) != 0) {
    /* handle error */
  }
  printf("Could not open file because of %s\n", errmsg);
}

Note that because of the optional nature of Annex K, strerror_s() may not be available in all implementations. 

Risk Assessment

Race conditions caused by multiple threads invoking the same library function can lead to abnormal termination of the application, data integrity violations, or denial-of-service attack.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

CON33-C

medium

probable

high

P4

L3

Automated Detection

Tool

Version

Checker

Description

Compass/ROSE

 

 

A module written in Compass/ROSE can detect violations of this rule.

Related Guidelines

CERT C++ Secure Coding Standard: CON03-CPP. Avoid assuming functions are thread safe unless otherwise specified.

ISO/IEC 9899:2011 Section 7.22.2.1, "The rand function," Section 7.22.4.6, "The getenv function," Section 7.24.5.8, "The strtok function," Section 7.24.6.2, "The strerror function," Section 7.27.3.1, "The asctime function," Section 7.27.3.2, "The ctime function"

Sources

 [Historical information about POSIX.1 Thread Safety]

 [ISO/IEC 9899:2011], tSection K.3.7.4.2, "The strerror_s function"