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 K
strtok_r() in POSIX
strerror() strerror_s() in C11 Annex K
strerror_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 K
tmpnam_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"
Excerpt from NVIDIA CUDA C++ Guidelines for robust and safety-critical programming, Version 3.0.1, Copyright (C) 2018-2023 NVIDIA Corporation.

Possible Messages

Key

Text

Severity

Disabled

non_thread_safe_function_use

Avoid race conditions when using library functions.{}

None

False

Options

func_when_with_null

func_when_with_null

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
}
functions need not be thread-safe if passed a NULL as n'th argument.
 

funcs

funcs : set[bauhaus.analysis.config.FunctionName] = {'round'}

functions need not be thread-safe.
 

remediations

remediations

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()'
}
Mapping from function names to remediation descriptions.