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.

FunctionsRemediation
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.26.6.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, 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 Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Automated Detection

Tool

Version

Checker

Description

Astrée
24.04

Supported, but no explicit checker
CodeSonar
8.1p0

BADFUNC.RANDOM.RAND
BADFUNC.TEMP.TMPNAM
BADFUNC.TTYNAME

Use of rand (includes check for uses of srand())
Use of tmpnam (includes check for uses of tmpnam_r())
Use of ttyname

Compass/ROSE



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

Cppcheck Premium

24.9.0

premium-cert-con33-cFully implemented
Helix QAC

2024.3

C5037

C++5021

DF4976, DF4977


Klocwork
2024.3

CERT.CONC.LIB_FUNC_USE


LDRA tool suite
 9.7.1
44 SPartially Implemented
Parasoft C/C++test
2023.1

CERT_C-CON33-a

Avoid using thread-unsafe functions

PC-lint Plus

1.4

586

Fully supported

Polyspace Bug Finder

R2024a

CERT C: Rule CON33-CChecks 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 StandardERR30-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 failurePrior to 2018-01-12: CERT: Unspecified Relationship
CERT CCON00-CPP. Avoid assuming functions are thread safe unless otherwise specifiedPrior to 2018-01-12: CERT: Unspecified Relationship
CWE 2.11CWE-3302017-06-28: CERT: Partial overlap
CWE 2.11CWE-3772017-06-28: CERT: Partial overlap
CWE 2.11CWE-6762017-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 strerror Function" 

[Open Group 1997b]Section 10.12, "Thread-Safe POSIX.1 and C-Language Functions"




27 Comments

  1. I think this can be written as a C1X rule, and not as a POSIX rule. I think all that is needed is to replace the pthreads with C1X threads. In fact, we should probably just go ahead and create a new section on concurrency.

    I think the first paragraph with this rule should start with the general description of the project. Then, I think the emphasis on rand() including the quote is too much, because this is just one of many functions with exactly the same problem, right?

    In the noncompliant example, I would like to see a little better description of why the rand function is not required to avoid data races (presumably because it needs to access a global variable)?

    The "it is possible for them to result in more serious vulnerabilities" is sort of vague. Perhaps you can provide a "such as..."

    1. I have tried to incorporate the suggestions in the rule.
      Do I need to change pthreads in the NCCE and CS code and use library functions and macros in threads.h as per Section 7.24 Threads in N1401-C1X ?

  2. [comment updated - the original version had some muddled thinking about rand_r()]

    The code shown in the compliant solution does not solve the problem. Although not likely for rand(), an implementation can have thread safe functions which access the same internal data as a non-thread-safe function, but the thread safe functions protect the data with an internal mutex. This means that not only can an application not call the non-thread-safe function from different threads at the same time, it can't call the non-thread-safe function and any other function at the same time. The proper solution is not to call non-thread-safe functions at all in multithreaded programs (except perhaps in the initial thread before creating any more threads).

    I don't know whether C1X has rand_r(). If it doesn't, then perhaps this should remain a POSIX rule and the compliant solution should show the use of rand_r(). Alternatively change the examples to be based on a function for which C1X does have an _r equivalent.

    1. The CS code does solve the problem, but only if no other (thread-unsafe) functions call rand(), or access its global data. Which the CS should state. C99 guarentees that no other library functions act as if they call rand(), so theoretically you can build thread-safe programs using a mutex on rand().

      C1x does not have rand_r().

      Unfortunately, the problem is deeper, because usage of rand() violates MSC30-C. Do not use the rand() function for generating pseudorandom numbers. That rule recommends POSIX random() (and a similar function for Windows). But random() is not thread-safe. GNU provides a random_r() function, but I could not get it to work (in my last 5 minutes at work (smile)

      1. The fact that C99 guarantees no other library functions act as if they call rand() does not make it safe to call rand() at the same time as thread-safe functions. The following is a contrived example, but hopefully it serves to illustrate the problem.

        Suppose an implementation of rand() has a trace mode that can be activated by setting an environment variable, whereby it writes information about each call to stderr. Since rand() is not thread safe it can write to stderr without locking the stream. If one thread calls rand() at the same time as another thread is writing to stderr, rand() will update the stream internals even though they are locked by the other thread.

        Does C1X have strerror_r()? That would be a good choice for the examples.

        1. C1X does not support strerror_r(), but POSIX.1-2001 does. So I've made NCCE/CS using strerror(). I found the following problems with other functions:

          rand()

          violates MSC30-C

          getenv()

          no _r equivalent

          strtok()

          strtok_r(3) discourages use

          asctime()

          POSIX.1-2008 marks asctime() and asctime_r() as obsolete

          ctime()

          Ditto

          strerror()

          Specified in POSIX.1-2001. Also a GNU extension

          1. This is an interesting table in that I think we need to deal with each function in this table in some way. If we have a guideline that says "don't use it" that effectively deals with it.

            as you say, MSC30-C. Do not use the rand() function for generating pseudorandom numbers, prohibits the use of rand() (for any purpose really)

            asctime() and ctime() are both listed as obsolescent in "MSC34-C. Do not use deprecated or obsolescent functions"

            There is an interesting article on thread safety of {{getenv()} at: http://blogs.sun.com/pgdh/date/20050614

            So we currently have a POSIX only solution for strerror() but I think we should also have one or more C1X solutions for getenv()} and {{strerror() that use the C1X mtx_lock() function defined in Section 7.24.4.3 of the current working draft and possibly other C1X based solutions.

  3. The thread-safe version of strerror() also has problems of its own. POSIX defines strerror_r() to return an integer (0 = success, -1 = failure + changes to errno), whereas Linux returns the formatted string. Rule ERR30-C partially addresses this (i.e. set errno to 0 prior to calling strerror_r()), but also by this same rule we shouldn't be checking the value of errno unless we first check the response from strerror_r().

    1. POSIX strerror_r() returns 0 on success or an error number on failure. It does not set errno.

      Linux (glibc) has two versions of strerror_r(), one conforming and one not. You get the conforming one if you compile applications in the way POSIX requires (i.e. by defining _POSIX_C_SOURCE or _XOPEN_SOURCE appropriately).

      1. The XSI version of strerror_r() does set errno. The GNU version of sterror_r() does not.

        I've made both NCCCE & CS compliant with ERR30-C and xrefed it. Also added a note about the XSI vs GNU versions of strerror_r() on Linux.

        1. I like this, but I also like Geoff's suggestion of defining _POSIX_C_SOURCE or _XOPEN_SOURCE appropriately.

          There is currently a slight disconnect with the following statement:

          Check your strerror_r() man page to see which version is available on your system.

          One suggests that you can always do this, and one suggest that it is implementation defined.

          Which is correct?

          1. I think the simplest solution would be to change "which version is available" to "which versions are available" or "which version(s) are available".

            1. I'm a big fan of easy. 8^) Done.

        2. The XSI version of strerror_r() does set errno.

          Please read the standard. Last paragraph under RETURN VALUE:

          Upon successful completion, strerror_r() shall return 0. Otherwise, an error number shall be returned to indicate the error.

          Perhaps what confused you is that strerror_r() has an ERANGE error listed in the ERRORS section, and you assumed that this section always means errno is set to the listed value. You have to read the RETURN VALUE section to see how the errors are indicated. For strerror_r() the error number is in the return value of the function. It is NOT required to set errno.

          1. My Linux manpage strerror(3) says (in the last paragraph of RETURN VALUE section):

            The XSI-compliant strerror_r() function returns 0 on success; on error, -1 is returned and errno is set to indicate the error.
            {quote]

            So yes I am definitely confused :-S It's also not the first time we've found inconsistent documentation wrt errno.

            Howeve, we don't need to rely on errno for this rule, we can just check the return value instead. If a function both sets errno and returns an out-of-band error indicator (such as NULL), we prefer checking the indicator, as it is more reliable and harder to ignore (and better documented). This is all addressed in ERR30-C.

            1. My Linux manpage strerror(3) says ...

              That's either a mistake in the man page, or a bug in glibc. Clearly the intention is for the "XSI" version of strerror_r() to conform to the standard. If it doesn't, then presumably whoever wrote it misread the standard.

              Does your Linux system have an up to date glibc? Maybe it's been fixed since the version you have.

  4. FYI I have submitted a defect report to the Austin Group regarding the thread safety of getenv() in POSIX.

    http://austingroupbugs.net/view.php?id=188

    If this change is accepted, then I would hope that C1X will follow suit.

    1. Let me know if the change is accepted, and of course, Nick Stoughton. I would be happy to bring a proposal to Italy recommending this change.

      1. The Austin Group decided to make a bigger change: disallow copying to an internal buffer. The thinking here is that since POSIX requires the environment variables to be available via environ[], there is no reason why getenv() can't return a pointer to the actual environment instead of a copy.

        The thread-safety of getenv() is affected the same way by this change as by the original request (to require per-thread buffers if copying is done), but since the C Standard does not have environ[] I imagine it would be less likely to be accepted by the C1X committee. You (or Nick) might still consider proposing per-thread buffers for C1X.

  5. There are still a couple of problems with this rule:

    • fopen() is not guaranteed to set errno in C11. (it is guaranteed in POSIX).
    • The first CS still possesses a race window on errno. The window starts on the call to fopen() and ends at the beginning of the call to strerror_r(). This is because in C99 (and presumably POSIX), errno is static. So it can be modified by another thread during the race window. In C11 errno is thread-local, so the 2nd CS has no such race window.
    1. I've addressed these issues.

    2. There is no race window on errno in POSIX.  See XSH 2.3 Error Numbers:

      "For each thread of a process, the value of errno shall not be affected by function calls or assignments to errno by other threads."

      1. Oops, you're right. So the strerror_r code is compliant, and we don't need the mutex example. Fixed.

  6. I think this note:

    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.

    Might be misconstued to mean "list of C Standard " functions.  If not, this should be restated more clearly, e.g.:

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

    1. Are there no Standard C functions in the POSIX list? Sorry, don't have a copy of POSIX.1. If there are Standard C functions in the POSIX list. Maybe the words should be:

      Section 2.9.1 of the Systems Interface volume of POSIX.1-2008 has a list of additional functions that are not required to be thread-safe.

    1. OK, so this list augments the C list. I think I remember seeing some words used in one of the rules that mentions that POSIX extends the C Standard list.