CERT
Skip to end of metadata
Go to start of metadata

Accessing or modifying shared objects in signal handlers can result in race conditions that can leave data in an inconsistent state. The two exceptions (C Standard, 5.1.2.3, paragraph 5) to this rule are the ability to read from and write to lock-free atomic objects and variables of type volatile sig_atomic_t. Accessing any other type of object from a signal handler is undefined behavior. (See undefined behavior 131.)

The need for the volatile keyword is described in DCL22-C. Use volatile for data that cannot be cached.

The type sig_atomic_t is the integer type of an object that can be accessed as an atomic entity even in the presence of asynchronous interrupts. The type of sig_atomic_t is implementation-defined, though it provides some guarantees. Integer values ranging from SIG_ATOMIC_MIN through SIG_ATOMIC_MAX, inclusive, may be safely stored to a variable of the type. In addition, when sig_atomic_t is a signed integer type, SIG_ATOMIC_MIN must be no greater than −127 and SIG_ATOMIC_MAX no less than 127. Otherwise, SIG_ATOMIC_MIN must be 0 and SIG_ATOMIC_MAX must be no less than 255. The macros SIG_ATOMIC_MIN and SIG_ATOMIC_MAX are defined in the header <stdint.h>.

According to the C99 Rationale [C99 Rationale 2003], other than calling a limited, prescribed set of library functions,

the C89 Committee concluded that about the only thing a strictly conforming program can do in a signal handler is to assign a value to a volatile static variable which can be written uninterruptedly and promptly return.

However, this issue was discussed at the April 2008 meeting of ISO/IEC WG14, and it was agreed that there are no known implementations in which it would be an error to read a value from a volatile sig_atomic_t variable, and the original intent of the committee was that both reading and writing variables of volatile sig_atomic_t would be strictly conforming.

The signal handler may also call a handful of functions, including abort(). (See SIG30-C. Call only asynchronous-safe functions within signal handlers for more information.)

Noncompliant Code Example

In this noncompliant code example, err_msg is updated to indicate that the SIGINT signal was delivered.  The err_msg variable is a character pointer and not a variable of type volatile sig_atomic_t.

Compliant Solution (Writing volatile sig_atomic_t)

For maximum portability, signal handlers should only unconditionally set a variable of type volatile sig_atomic_t and return, as in this compliant solution:

Compliant Solution (Lock-Free Atomic Access)

Signal handlers can refer to objects with static or thread storage durations that are lock-free atomic objects, as in this compliant solution:

Exceptions

SIG31-C-EX1:  The C Standard, 7.14.1.1 paragraph 5 [ISO/IEC 9899:2011], makes a special exception for errno when a valid call to the signal() function results in a SIG_ERR return, allowing errno to take an indeterminate value. (See ERR32-C. Do not rely on indeterminate values of errno.)

Risk Assessment

Accessing or modifying shared objects in signal handlers can result in accessing data in an inconsistent state. Michal Zalewski's paper "Delivering Signals for Fun and Profit" [Zalewski 2001] provides some examples of vulnerabilities that can result from violating this and other signal-handling rules.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

SIG31-C

High

Likely

High

P9

L2

Automated Detection

Tool

Version

Checker

Description

Astrée17.04i Supported, but no explicit checker
CodeSonar4.4CONCURRENCY.DATARACEData race
Compass/ROSE

 

 

Can detect violations of this rule for single-file programs

LDRA tool suite9.5.6

87 D

Fully implemented

Related Vulnerabilities

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

Related Guidelines

ISO/IEC TS 17961:2013Accessing shared objects in signal handlers [accsig]
MITRE CWECWE-662, Improper Synchronization

Bibliography

[C99 Rationale 2003]5.2.3, "Signals and Interrupts"
[ISO/IEC 9899:2011]Subclause 7.14.1.1, "The signal Function"
[Zalewski 2001] 

 


7 Comments

  1. Yes, that's right. A signal handler can store into a volatile sig_atomic_t of static storage duration but cannot read it. SIG00-A has one such access and it documents its violation of SIG31-C. If an NCCE surfaces that doesn't violate SIG31-C, so much the better.

    Signal handlers can access all of their parameters and local variables. Only objects of static storage duration are off limits. For the purposes of this restriction, objects in the heap count as having static storage duration. The purpose is to allow all accesses in the signal handler's stack frame, and disallow everything but a store to a volatile sig_atomic_t everywhere else.

    Of course, this is just for maximally portable code. Most systems allow their own set of additional actions.

    1. Text and compliant example now both get & set static volatile sig_atomic_t values

  2. Mitigation Strategies

    Static Analysis

    Compliance with this rule can be checked using structural static analysis checkers using the following algorithm:

    1. Traverse the abstract syntax tree (AST) to identify function calls to the signal function signal(int, void (*f)(int)).
    2. At each function call to signal(int, void (*f)(int)) get the second argument from the argument list. To make sure that this is not an overloaded function the function type signature is evaluated and/or the location of the declaration of the function is verified to be from the correct file (because this is not a link-time analysis it is not possible to test the library implementation). Any definition for signal() in the application is suspicious, because it should be in a library.
    3. Perform a nested query to identify all referenced objects with static storage duration. Verify that none of these objects are referenced as an rvalue, and that for each object referenced as an lvalue, the underlying type is sig_atomic_t.
    4. Report any violations detected.
  3. It should be mentioned that _Exit (and probably abort) are not advisable, because atexit-registered handlers should be allowed to perform their intended functions before the program is terminated.

    Also, there seems to be no real reason for a signal handler to want to read a flag; setting it is sufficient.

    1. re: _Exit()/abort(). Didn't we have this conversation on SIG30-C?

      re: handlers reading flags. True, the examples didn't use to read flags. SIG00-C has example code that reads flags (implementing a finite state machine with them). The examples here do illustrate that reading sig_atomic_t flags is perfectly safe (if not particularly useful (smile)

  4. should we remove the note about "This may be necessary to ensure that the handler persists" since the cited recommendation tells us not to do this because of the race condition?

    1. Yes, I updated that paragraph, mainly deferring to SIG30, which discusses safe functions to call in signal handlers.