Some C standard library functions 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, the following library functions may contain data races when invoked by multiple threads:
API | Recommendation |
---|---|
rand() , srand() | MSC30-C. Do not use the rand() function for generating pseudorandom numbers |
getenv() , getenv_s() | ENV00-C. Do not store the pointer to the string returned by getenv() |
strtok() | strtok_s() in C11 Annex Kstrtok_r() in POSIX |
strerror() | strerror_s() in C11 Annex Kstrerror_r() in POSIX |
asctime() , ctime() ,localtime() , gmtime() | asctime_s() , ctime_s() , localtime_s() , gmtime_s() in C11 Annex K |
setlocale() | Protect multithreaded access to locale-specific APIs with a mutex |
ATOMIC_VAR_INIT , atomic_init() | Do not attempt to initialize an atomic variable from multiple threads |
tmpnam() | tmpnam_s() in C11 Annex Ktmpnam_r() in POSIX |
mbrtoc16() , c16rtomb() ,mbrtoc32() , c32rtomb() | Do not call with a null mbstate_t * argument |
Section 2.9.1 of the System Interfaces volume of POSIX.1-2008 augments the list of functions that are not required to be thread-safe [ISO/IEC/IEEE 9945:2008].
Noncompliant Code Example
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, subclause 7.24.6.2 [ISO/IEC 9899:2011], 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.
#include <errno.h> #include <stdio.h> #include <string.h> void f(FILE *fp) { fpos_t pos; errno = 0; if (0 != fgetpos(fp, &pos)) { char *errmsg = strerror(errno); printf("Could not get the file position 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.
Compliant Solution (Annex K, 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.
#define __STDC_WANT_LIB_EXT1__ 1 #include <errno.h> #include <stdio.h> #include <string.h> enum { BUFFERSIZE = 64 }; void f(FILE *fp) { fpos_t pos; errno = 0; if (0 != fgetpos(fp, &pos)) { char errmsg[BUFFERSIZE]; if (strerror_s(errmsg, BUFFERSIZE, errno) != 0) { /* Handle error */ } printf("Could not get the file position because of %s\n", errmsg); } }
Note that because Annex K is optional, strerror_s()
may not be available in all implementations.
Compliant Solution (POSIX, strerror_r()
)
This compliant solution uses the POSIX strerror_r()
function, which has the same functionality as strerror()
but guarantees thread-safety:
#include <errno.h> #include <stdio.h> #include <string.h> enum { BUFFERSIZE = 64 }; void f(FILE *fp) { fpos_t pos; errno = 0; if (0 != fgetpos(fp, &pos)) { char errmsg[BUFFERSIZE]; if (strerror_r(errno, errmsg, BUFFERSIZE) != 0) { /* Handle error */ } printf("Could not get the file position because of %s\n", errmsg); } }
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, which you will get if you compile your application as required by POSIX (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.
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 |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Automated Detection
Tool | Version | Checker | Description |
---|---|---|---|
|
| A module written in Compass/ROSE can detect violations of this rule |
Related Guidelines
Bibliography
[ISO/IEC 9899:2011] | Subclause 7.24.6.2, "The |
[Open Group 1997b] | Section 10.12, "Thread-Safe POSIX.1 and C-Language Functions" |