
...
Type qualifying objects as volatile does not guarantee synchronization between multiple threads, protect against simultaneous memory accesses, or, 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 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 | ||||
---|---|---|---|---|
| ||||
#include <signal<stddef.h> #include <stdint.h> sig_atomicextern void get_register_bank(int32_t interrupted;**bank, /* Bug: not declared volatile */ void sigint_handler(int signum) { interrupted = 1; /* Assignment may not be visible in main() */ } int main(void) { signal(SIGINT, sigint_handlersize_t *num_registers); extern void while (!interrupted) { /* Loop may never terminate */ /* ... */ } 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.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <signal.h> volatile sig_atomic_t interrupted; void sigint_handler(int signum) { interrupted = 1; } int main(void) { signal(SIGINT, sigint_handler); while (!interrupted) { /* ...external_wait(void); void func(void) { int32_t bank[3]; size_t num_regs = 3; get_register_bank((int32_t **)&bank, &num_regs); if (num_regs < 3) { /* Handle error */ } bank[0] = 1; external_wait(); returnbank[0] = 0; } |
The sig_atomic_t
type 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
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>
.
Noncompliant Code Example (Cast to volatile
)
In this noncompliant code example, the thread_func
function 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 does not need to be flushed from a register into memory and consequently may not be visible when read despite the cast. As a result, the loop may never terminate.
Code Block | ||||
---|---|---|---|---|
| ||||
extern int compute(void*);
static _Bool end_processing;
int 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, end_processing
is guaranteed to be read from and written to its original address for every iteration of the while
loop.
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 | ||||
---|---|---|---|---|
| ||||
#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 | ||||
| ||||
extern int compute(void*); static volatile _Bool end_processing; int thread_func(void *arg) { while (0 == end_processing) { int status; status = compute(arg); if (status) { /* NotifyHandle other threads to end processing error */ } end_processing bank[0] = 1; breakexternal_wait(); bank[0] } } return = 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 violationsFailure to declare variables containing data that cannot be cached as volatile can result in unexpected runtime behavior resulting from compiler optimizations.
Recommendation | Severity | Likelihood |
---|
Detectable | Repairable | Priority | Level |
---|---|---|---|
DCL22-C | Low | Probable | No |
Yes |
P4 | L3 |
Automated Detection
Tool | Version | Checker | Description |
---|
LDRA tool suite |
|
|
|
8 D | Partially implemented | ||||||||
Parasoft C/C++test |
| CERT_C-DCL22-a | Avoid unused values | ||||||
Polyspace Bug Finder |
| 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.
Related Guidelines
CERT C Secure Coding Standard | SIG31-C. Do not access shared objects in signal handlers |
SEI CERT C++ |
Coding Standard |
VOID DCL20-CPP. Use volatile for data that cannot be cached | |
MISRA C:2012 | Rule 2.2 (required) |
Bibliography
[C99 Rationale 2003] | Subclause 6.7.3, "Type Qualifiers" |
...
...