Some functions in the C standard library functions are not guaranteed to be reentrant with respect to threads. Some functions (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.
As per the N1401-C1X documentAccording to the C Standard, the following library functions are not required to avoid data races:
...
listed in the following table may contain data races when invoked by multiple threads.
Functions | Remediation |
---|---|
rand() , srand() | MSC30-C. Do not use the rand() function for generating pseudorandom numbers |
getenv() , getenv_s() | ENV34-C. Do not store pointers returned by certain functions |
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 functions 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 has a much longer list Portable Operating System Interface (POSIX®), Base Specifications, Issue 7 [IEEE Std 1003.1:2013] extends the list of functions that are not required to be thread-safe.
Noncompliant Code Example
Consider In this noncompliant code example, the function f()
is called from within a multithreaded application that but encounters an error while calling a system function. The strerror()
function returns a human-readable error string given an error number. According to C99, Section
The C Standard, 7.2226.6.2 3 paragraph 3 [ISO/IEC 9899:2024], specifically states that strerror()
is not required to avoid data races.
The strerror function is not required to avoid data races with other calls to the strerror function.
An implementation could write the error string into a static array and return a pointer to it. 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.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <errno.h> errno = 0; FILE* fd = fopen( filename, "r"); if (fd == NULL) { char* #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 openthe file because ofposition: %s\n", errmsg); } } |
Note that this 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 (POSIX)
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:
Code Block | ||||
---|---|---|---|---|
| ||||
#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: %s\n", errmsg);
}
} |
Because Annex K is optional, strerror_s()
may not be available in all implementations.
Compliant Solution (POSIX, strerror_r()
)
This The compliant solution uses the POSIX strerror_r()
function, which has the same functionality as strerror()
but guarantees thread safety.:
Code Block | ||||
---|---|---|---|---|
| ||||
errno = 0; FILE* fd = fopen( filename, "r"); if (fd == NULL#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[BUFSIZBUFFERSIZE]; if (strerror_r(errno, errmsg, BUFSIZBUFFERSIZE) != 0) { /* handleHandle error */ } printf("Could not openget the file because ofposition: %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. You can get the XSI-compliant version if you compile applications in the way POSIX requires , which is the default when an application is compiled as required by POSIX (that is, by defining _POSIX_C_SOURCE
or _XOPEN_SOURCE
appropriately). Check your The strerror_r()
man page to see which version(s) manual page lists versions that are available on your a particular 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 a denial-of-service attack.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
CON33-C |
Medium |
Probable |
High | P4 | L3 |
Other Languages
...
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
Astrée |
| Supported, but no explicit checker | |||||||
CodeSonar |
| BADFUNC.RANDOM.RAND | Use of | ||||||
Compass/ROSE | A module written in Compass/ROSE can detect violations of this rule | ||||||||
Cppcheck Premium |
| premium-cert-con33-c | Fully implemented | ||||||
Helix QAC |
| C5037 C++5021 DF4976, DF4977 | |||||||
Klocwork |
| CERT.CONC.LIB_FUNC_USE | |||||||
LDRA tool suite |
| 44 S | Partially Implemented | ||||||
Parasoft C/C++test |
| CERT_C-CON33-a | Avoid using thread-unsafe functions | ||||||
PC-lint Plus |
| 586 | Fully supported | ||||||
| CERT C: Rule CON33-C | Checks for data race through standard library function call (rule fully covered) |
Related Guidelines
Key here (explains table format and definitions)
Taxonomy | Taxonomy item | Relationship |
---|---|---|
CERT C Secure Coding Standard | 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 | Prior to 2018-01-12: CERT: Unspecified Relationship |
CERT C | CON00-CPP. Avoid assuming functions are thread |
...
safe unless otherwise specified | Prior to 2018-01-12: CERT: Unspecified Relationship |
CWE 2. |
...
11 | CWE-330 | 2017-06-28: CERT: Partial overlap |
CWE 2.11 | CWE-377 | 2017-06-28: CERT: Partial overlap |
CWE 2.11 | CWE-676 | 2017-05-18: CERT: Rule subset of CWE |
CERT-CWE Mapping Notes
Key here for mapping notes
CWE-330 and CON33-C
Independent( MSC30-C, MSC32-C, CON33-C)
Intersection( CWE-330, CON33-C) =
- Use of rand() or srand() from multiple threads, introducing a race condition.
CWE-330 – CON33-C =
- Use of rand() or srand() without introducing race conditions
- Use of other dangerous functions
CON33-C – CWE-330 =
- Use of other global functions (besides rand() and srand()) introducing race conditions
CWE-377 and CON33-C
Intersection( CWE-377, CON33-C) =
- Use of tmpnam() from multiple threads, introducing a race condition.
CWE-377 – CON33-C =
- Insecure usage of tmpnam() without introducing race conditions
- Insecure usage of other functions for creating temporary files (see CERT recommendation FIO21-C for details)
CON33-C – CWE-377 =
- Use of other global functions (besides tmpnam()) introducing race conditions
CWE-676 and CON33-C
- Independent( ENV33-C, CON33-C, STR31-C, EXP33-C, MSC30-C, ERR34-C)
- CON33-C lists standard C library functions that manipulate global data (e.g., locale()), that can be dangerous to use in a multithreaded context.
- CWE-676 = Union( CON33-C, list) where list =
- Invocation of the following functions without introducing a race condition:
- rand(), srand(, getenv(), getenv_s(), strtok(), strerror(), asctime(), ctime(), localtime(), gmtime(), setlocale(), ATOMIC_VAR_INIT, atomic_init(), tmpnam(), mbrtoc16(), c16rtomb(), mbrtoc32(), c32rtomb()
- Invocation of other dangerous functions
Bibliography
[IEEE Std 1003.1:2013] | Section 2.9.1, "Thread Safety" |
[ISO/IEC 9899:2024] | Subclause 7.26.6.3, "The |
[Open Group 1997b] | Section 10.12, "Thread-Safe POSIX.1 and C-Language Functions" |
...
Automated Detection
A module written in Compass/ROSE can detect violations of this rule.
References
Wiki Markup |
---|
\[[N1401-C1X Draft|http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1401.pdf]\] Section 7.21.2.1 rand() function, Section 7.21.4.6 getenv() function, Section 7.22.5.8 strtok() function, Section 7.22.6.2 strerror() function, Section 7.25.3.1 asctime() function, Section 7.25.3.2 ctime() function
\[[Historical information about POSIX.1 Thread Safety|http://www.unix.org/whitepapers/reentrant.html]\] |
50. POSIX (POS) POS41-C. When a thread exit status is not of concern, pthread_detach() or an equivalent function must be used