CUDASecurity-CON03¶
Avoid race conditions when using library functions
Required inputs: IR
Some C standard library functions are not guaranteed to be
reentrant
with respect to threads. 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 library functions 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 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
In this noncompliant code example, the function
f() is called from within a multithreaded application but
encounters an error while calling a system function. The
strerror() function returns a human-readable error string given an
error number. The C Standard, 7.24.6.2 [
ISO/IEC
9899:2011], specifically states that
strerror() is not required to avoid data races. An
implementation
could write the error string into a static array and return a pointer to it,
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: %s\n", errmsg);
}
}
This code first sets
errno to 0 to comply with
ERR30-C.
Take care when reading errno.
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: %s\n", errmsg);
}
}
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: %s\n", errmsg);
}
}
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 is the default when an application is compiled as
required by POSIX (that is, by defining
_POSIX_C_SOURCE or
_XOPEN_SOURCE appropriately). The
strerror_r() manual page lists versions that are available
on 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 |
Related Guidelines
| 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 |
Bibliography
| [ IEEE Std 1003.1:2013] | Section 2.9.1, "Thread Safety" |
| [ ISO/IEC 9899:2011] | Subclause 7.24.6.2, "The
strerror Function" |
| [ Open Group 1997b] | Section 10.12, "Thread-Safe POSIX.1 and C-Language Functions" |
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
non_thread_safe_function_use |
Avoid race conditions when using library functions.{} |
None |
False |
Options¶
This rule shares the following common options: exclude_in_macros, exclude_messages_in_system_headers, excludes, extend_exclude_to_macro_invocations, includes, justification_checker, languages, post_processing, provider, report_at, severity
The following places define options that affect this rule: Stylechecks, Analysis-GlobalOptions
func_when_with_null¶
func_when_with_null
functions need not be thread-safe if passed a NULL as n'th argument.Type: dict[bauhaus.analysis.config.FunctionName, int]
Default:
{ 'ctermid': 0, 'mbrlen': 2, 'mbrtowc': 3, 'mbsnrtowcs': 4, 'mbsrtowcs': 3, 'tmpnam': 0, 'wcrtomb': 2, 'wcsnrtombs': 4, 'wcsrtombs': 3 }
funcs¶
funcs : set[bauhaus.analysis.config.FunctionName] = {'round'}
remediations¶
remediations
Mapping from function names to remediation descriptions.Type: dict[bauhaus.analysis.config.FunctionName, str]
Default:
{ 'ATOMIC_VAR_INIT': 'Do not attempt to initialize an atomic variable from multiple threads', 'asctime': 'Use asctime_s(), ctime_s() or gmtime_s()', 'atomic_init': 'Do not attempt to initialize an atomic variable from multiple threads', 'c16rtomb': 'Do not call with a null mbstate_t * argument', 'c32rtomb': 'Do not call with a null mbstate_t * argument', 'ctime': 'Use asctime_s(), ctime_s() or gmtime_s()', 'getenv': 'Do not store pointers returned by certain functions', 'getenv_s': 'Do not store pointers returned by certain functions', 'gmtime': 'Use asctime_s(), ctime_s() or gmtime_s()', 'localtime': 'Use asctime_s(), ctime_s() or gmtime_s()', 'mbrtoc16': 'Do not call with a null mbstate_t * argument', 'mbrtoc32': 'Do not call with a null mbstate_t * argument', 'rand': 'Do not use the rand() function for generating pseudorandom numbers', 'round': 'Use rint() in CUDA device code to round to a double-precision floating-point number as this maps to a single instruction', 'setlocale': 'Protect multithreaded access to locale-specific functions with a mutex', 'srand': 'Do not use the rand() function for generating pseudorandom numbers', 'strerror': 'Use strerror_s() in C11 or strerror_r() in POSIX', 'strtok': 'Use strerror_s() in C11 or strerror_r() in POSIX', 'tmpnam': 'Use tmpnam_s()' }