Document Number: N2228
Submitter: Florian Weimer
Submission Date: 2018-03-26
Subject: Optional thread storage duration for internal function state

Summary

This document proposes to prevent strictly conforming programs to use pointer values returned from certain library functions after the thread that originally called them has exited. It is related to papers N2225, N2226, N2227.

The core observation which motivates this document is that while the standard does not require to detect data races involving certain library functions, it does rule out a straightforward implementation of these library functions based on objects of thread storage duration. Returning a pointer to such objects does not work because the objects cease to exist once the thread exits.

  1. The strerror function is widely used in thread-aware programs (which use threads and synchronization in other ways). It is much simpler to use than the strerror_r function provided by some implementations (whose use is further complicated by varying prototypes between implementations).

    Since reading the returned string happens after the strerror function has returned, merely avoid data races within the function is insufficient. It also would not address the higher-level race condition; threads would still obtain an incorrect error message generated by another thread.

    As an alternative approach, an implementation could avoid deallocating the object storing the error message when the thread exits, and reuse it for a future call to the strerror function. However, that introduces substantial complexity for a very small gain.

    If an implementation does not include an unknown error number in the result of the strerror function and takes some precautions about changing the program's locale, a thread-safe implementation is already possible because it is not necessary to provide backing storage for the result: all possible result strings are known in advance and can be precomputed.

    POSIX defines both a strerror_r and a strerror_l function. Both are required to be thread-safe (but strerror_l cannot be used with the global locale handle, i.e., the program's locale). There is no discussion what happens when the thread calling strerror_l exits; presumably the previously returned pointer becomes invalid, as POSIX mentions in the context of the setlocale function.

  2. The time conversion functions face a very similar issue.

    Objects of thread storage duration are apparently already used in the Solaris implementation:

    The asctime(), ctime(), gmtime(), and localtime() functions are safe to use in multithread applications because they employ thread-specific data. [Source]

    However, it is unclear whether this is at the expense of making undefined subsequent pointer access after thread exit.

    POSIX suggests that a thread-safe implementation of these functions is possible, but does not mention the behavior on thread exit as far as the returned pointer is concerned (which it does for the setlocale function).

Proposed Resolution

  1. In 7.24.6.2 (The strerror function), add:
    The strerror function returns a pointer to the string, the contents of which are locale- specific. The array pointed to shall not be modified by the program, but may be overwritten by a subsequent call to the strerror function. If the pointer is accessed after the thread which has called the strerror function has exited, the behavior is undefined.
    In J.2 (Undefined behavior), add:
    — Access to the pointer returned by the strerror function after the thread that originally called the function has exited (7.24.6.2).
  2. In 7.27.3 (Time conversion functions), add:
    Execution of any of the functions that return a pointer to one of these object types may overwrite the information in any object of the same type pointed to by the value returned from any previous call to any of them and the functions are not required to avoid data races with each other. 322) Accessing the returned pointer after the thread that called the function that returned it has exited results in undefined behavior. The implementation shall behave as if no other library functions call these functions.
    In J.2 (Undefined behavior), add:
    — Access to the pointer returned by the time conversion functions after the thread that originally called the function returning it has exited (7.27.3).