Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: REM Cost Reform

An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. Asynchronous signal handling, for example, may cause objects to be modified in a manner unknown to the compiler. Without this type qualifier, unintended optimizations may occur. These optimizations may cause race conditions , because a programmer may write code that prevents a race condition, yet the compiler is not aware of the programmer's data model , and may modify the code during compilation to permit race conditions.

Wiki MarkupThe {{volatile}} keyword eliminates this confusion by imposing restrictions on access and caching. According to the C99 Rationale \[[ISO/IEC 03|AA. Bibliography#ISO/IEC 03]\]:[C99 Rationale 2003],

No caching through this lvalue: each operation in the abstract semantics must be performed (that is, no cacheing caching assumptions may be made, because the location is not guaranteed to contain any previous value). In the absence of this qualifier, the contents of the designated location may be assumed to be unchanged except for possible aliasing.

While type Type qualifying objects as volatile ensures that a compiler does not perform unintended reordering or optimization, it in no way guarantees guarantee synchronization between multiple threads, ward protect against simultaneous memory accesses, or, and unless used to declare objects of type sig_atomic_t, guarantee atomicity of accesses to the object. For restrictions specific to signal handlers, see SIG31-C. Do not access or modify shared objects in signal handlers. However, type qualifying objects as volatile does ensure that a conforming compiler will not elide or reorder access to the object.

Noncompliant Code Example

...

In this This noncompliant code example relies on the reception of a SIGINT signal to toggle a flag to terminate a loop. However, because interrupted is not declared volatile, the read from it in main() may be optimized away by the compiler despite the assignment to the variable in the signal handler and the loop may never terminate. When compiled on GCC with the -O optimization flag, for example, the program fails to terminate even upon receiving a SIGINT, the programmer is targeting a custom piece of hardware that controls an LED by writing values into a register bank. The register bank is memory mapped into the process such that writing to a specific memory location will actually place a value into a hardware register to be read by the LED controller. The programmer intends to turn the LED on by placing value 1 into the first register, and then turn the LED off later by placing the value 0 into the first register.

Code Block
bgColor#ffcccc#FFcccc
langc
#include <stddef.h>
#include <signal<stdint.h>

sig_atomic_t interrupted;extern void get_register_bank(int32_t **bank,
        /* bug: not declared volatile */

void sigint_handler(int signum) {
  interrupted = 1;   /* assignment may not be visible in main() */
}

int main size_t *num_registers);
extern void external_wait(void);

void func(void) {
  signal(SIGINT, sigint_handlerint32_t bank[3];
  size_t num_regs = 3;

  get_register_bank((int32_t **)&bank, &num_regs);
  whileif (!interruptednum_regs < 3) {
    /* loopHandle mayerror never terminate */
   }

 /* do something */ bank[0] = 1;
  }external_wait();
  returnbank[0] = 0;
}

Noncompliant Code Example (Cast to volatile)

In this noncompliant code example, the function thread_func runs in the context of multiple threads designed to communicate with one another via the global variable end_processing. The function attempts to prevent the compiler from optimizing away the while loop condition by casting the variable to volatile before accessing it. However, because end_processing is not declared volatile, the assignment to it in the body of the loop need not be flushed from a register into memory and thus may not be visible when read despite the cast. As a result, the loop may never terminate.

Note, however, that declaring an object volatile is not sufficient to prevent data races when the object is simultaneously accessed from within two or more threads of execution. Additional memory visibility constraints may necessitate the use of platform-specific constructs such as memory barriers, for example when each of the threads runs on a different processor.

Code Block
bgColor#ffcccc

extern int compute(void*);

static _Bool end_processing;

void* thread_func(void *arg) {
  while (0 == *(volatile _Bool*)&end_processing) {
    int status;
    status = compute(arg);
    if (status) {
      /* notify other threads to end processing */
      end_processing = 1;
      break;
    }
  }
  return 0;
}

Compliant Solution

By adding the volatile qualifier to the variable declaration, interrupted is guaranteed to be accessed from its original address for every iteration of the while loop as well as from within the signal handler.

The compiler is free to optimize what it perceives as being a dead store to bank[0] by removing the first assignment to the variable. This would cause the LED to never be turned on in an optimized build. 

Compliant Solution

In this compliant solution, the register bank's memory is qualified with the volatile keyword, ensuring the compiler does not optimize access to the memory.

Code Block
bgColor#ccccff
langc
#include <stddef.h>
#include <stdint.h>

extern void get_register_bank(volatile int32_t **bank,
                              size_t *num_registers);
extern void external_wait(void);

void func(void) {
  volatile int32_t bank[3];
  size_t num_regs = 3;

  get_register_bank((volatile int32_t **)&bank, &num_regs);
  if (num_regs < 3
Code Block
bgColor#ccccff

#include <signal.h>

volatile sig_atomic_t interrupted;

void sigint_handler(int signum) {
  interrupted = 1;
}

int main(void) {
  signal(SIGINT, sigint_handler);
  while (!interrupted) {
    /* doHandle somethingerror */
   }

  bank[0] = 1;
  external_wait();
  returnbank[0] = 0;
}

...

Risk Assessment

Failing to use the volatile qualifier can result in race conditions in asynchronous portions of the code, causing unexpected values to be stored and leading to possible data integrity violations.

Failure to declare variables containing data that cannot be cached as volatile can result in unexpected runtime behavior resulting from compiler optimizations.

Recommendation

Rule

Severity

Likelihood

Detectable

Remediation Cost

Repairable

Priority

Level

DCL34

DCL22-C

Low

low

Probable

probable

No

high

Yes

P2

L3

P4

L3

Automated Detection

Tool

Version

Checker

Description

LDRA tool suite
 
Include Page
LDRA_V
LDRA_V
8 DPartially implemented
Parasoft C/C++test

Include Page
Parasoft_V
Parasoft_V

CERT_C-DCL22-a

Avoid unused values
Polyspace Bug Finder

Include Page
Polyspace Bug Finder_V
Polyspace Bug Finder_V

CERT C: Rec. DCL22-C


Checks for write without a further read (rule partially covered)

Related Vulnerabilities

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

Other Languages

...

Related Guidelines

...

...

...

Bibliography

...

...

[C99 Rationale 2003] Subclause 6.7.3,

...

"Type Qualifiers"


...

Image Added Image Added Image Added qualifiers," and Section 7.14, "Signal handling <signal.h>" \[[ISO/IEC 03|AA. Bibliography#ISO/IEC 03]\] Section 6.7.3, "Type qualifiers" \[[Sun 05|AA. Bibliography#Sun 05]\] [Chapter 6, "Transitioning to ISO C"|http://docs.sun.com/source/819-3688/tguide.html#pgfId-997898]DCL33-C. Ensure that restrict-qualified source and destination pointers in function arguments do not reference overlapping objects      02. Declarations and Initialization (DCL)      DCL35-C. Do not invoke a function using a type that does not match the function definition