CWE Rule 366
Description
Rule Description
If two threads of execution use a resource simultaneously, there exists the possibility that resources may be used while invalid, in turn making the state of execution undefined.
Polyspace Implementation
The rule checker checks for these issues:
Atomic load and store sequence not atomic
Atomic variable accessed twice in an expression
Data race
Data race on adjacent bit fields
Data race through standard library function call
Examples
Atomic load and store sequence not atomic
This checker is deactivated in a default Polyspace® as You Code analysis. See Checkers Deactivated in Polyspace as You Code Analysis (Polyspace Access).
This issue occurs when you use these functions to load, and then store an atomic variable.
C functions:
atomic_load()
atomic_load_explicit()
atomic_store()
atomic_store_explicit()
C++ functions:
std::atomic_load()
std::atomic_load_explicit()
std::atomic_store()
std::atomic_store_explicit()
std::atomic::load()
std::atomic::store()
A thread cannot interrupt an atomic load or an atomic store operation on a variable, but a thread can interrupt a store, and then load sequence.
A thread can modify a variable between the load and store operations, resulting in a data race condition.
To read, modify, and store a variable atomically, use a compound assignment operator
such as +=
, atomic_compare_exchange()
or
atomic_fetch_*
-family functions.
#include <stdatomic.h> #include <stdbool.h> static atomic_bool flag = ATOMIC_VAR_INIT(false); void init_flag(void) { atomic_init(&flag, false); } void toggle_flag(void) { bool temp_flag = atomic_load(&flag); temp_flag = !temp_flag; atomic_store(&flag, temp_flag); //Noncompliant } bool get_flag(void) { return atomic_load(&flag); }
In this example, variable flag
of type atomic_bool
is referenced twice inside the toggle_flag()
function. The function loads
the variable, negates its value, then stores the new value back to the variable. If two
threads call toggle_flag()
, the second thread can access
flag
between the load and store operations of the first thread.
flag
can end up in an incorrect state.
One possible correction is to use a compound assignment operator to toggle the value
of flag
. The C standard defines the operation by using
^=
as atomic.
#include <stdatomic.h> #include <stdbool.h> static atomic_bool flag = ATOMIC_VAR_INIT(false); void toggle_flag(void) { flag ^= 1; } bool get_flag(void) { return flag; }
Atomic variable accessed twice in an expression
This checker is deactivated in a default Polyspace as You Code analysis. See Checkers Deactivated in Polyspace as You Code Analysis (Polyspace Access).
This issue occurs
when C atomic types or C++
std::atomic
class variables appear twice in an expression and
there are:
Two atomic read operations on the variable.
An atomic read and a distinct atomic write operation on the variable.
The C standard defines certain operations on atomic variables that are thread safe and do not cause data race conditions. Unlike individual operations, a pair of operations on the same atomic variable in an expression is not thread safe.
A thread can modify the atomic variable between the pair of atomic operations, which can result in a data race condition.
Do not reference an atomic variable twice in the same expression.
#include <stdatomic.h> atomic_int n = ATOMIC_VAR_INIT(0); int compute_sum(void) { return n * (n + 1) / 2; //Noncompliant }
In this example, the global variable n
is referenced twice in the
return statement of compute_sum()
. The value of n
can
change between the two distinct read operations. compute_sum()
can return
an incorrect value.
One possible correction is to pass the variable as a function argument
n
. The variable is copied to memory and the read operations on the
copy guarantee that compute_sum()
returns a correct result. If you pass
a variable of type int
instead of type atomic_int
,
the correction is still valid.
#include <stdatomic.h> int compute_sum(atomic_int n) { return n * (n + 1) / 2; }
Data race
This checker is deactivated in a default Polyspace as You Code analysis. See Checkers Deactivated in Polyspace as You Code Analysis (Polyspace Access).
This issue occurs when:
Multiple tasks perform unprotected operations on a shared variable.
At least one task performs a write operation.
At least one operation is nonatomic. To detect data race on both atomic and nonatomic operations, use the options
-detect-atomic-data-race
. See Extend Data Race Checkers to Atomic Operations.
If you activate this checker without specifying the multitasking options first, you see a warning in the log:
Warning: Checker 'Data Race' is activated but no protection have been defined
To specify these options, on the Configuration pane, select Multitasking. For more information, see Configuring Polyspace Multitasking Analysis Manually.
Data race can result in unpredictable values of the shared variable because you do not control the order of the operations in different tasks.
Data races between two write operations are more serious than data races between a write and read operation. Two write operations can interfere with each other and result in indeterminate values. To identify write-write conflicts, use the filters on the Detail column of the Results List pane. For these conflicts, the Detail column shows the additional line:
Variable value may be altered by write-write concurrent access.
To fix this defect, protect the operations on the shared variable using critical sections, temporal exclusion or another means. See Protections for Shared Variables in Multitasking Code.
To identify existing protections that you can reuse, see the table and graphs associated with the result. The table shows each pair of conflicting calls. The Access Protections column shows existing protections on the calls. To see the function call sequence leading to the conflicts, click the icon. For an example, see below.
Extend this checker to check for data races in operations that Bug Finder might not detect by default. For instance:
You might be using multithreading functions that are not supported by Polyspace. Extend this checker by mapping the functions of your multithreading functions to their known POSIX® equivalent. See Extend Concurrency Defect Checkers to Unsupported Multithreading Environments.
Polyspace assumes that certain operations are atomic and excludes them from data race checks. See Define Atomic Operations in Multitasking Code. These assumptions might not apply to your environment. To extend the data race checkers to include these operations, use the option
-detect-atomic-data-race
. See Extend Data Race Checkers to Atomic Operations.
int var; //Noncompliant void begin_critical_section(void); void end_critical_section(void); void increment(void) { var++; } void task1(void) { increment(); } void task2(void) { increment(); } void task3(void) { begin_critical_section(); increment(); end_critical_section(); }
In this example, to emulate multitasking behavior, specify the following options:
Option | Specification | |
---|---|---|
Configure multitasking manually | ||
Tasks (-entry-points) |
| |
Critical section details (-critical-section-begin -critical-section-end) | Starting routine | Ending routine |
begin_critical_section | end_critical_section |
On the command-line, you can use the following:
polyspace-bug-finder -entry-points task1,task2,task3 -critical-section-begin begin_critical_section:cs1 -critical-section-end end_critical_section:cs1
In this example, the tasks task1
, task2
,
and task3
call the function increment
. increment
contains
the operation var++
that can involve multiple machine
instructions including:
Reading
var
.Writing an increased value to
var
.
These machine instructions, when executed from task1
and task2
,
can occur concurrently in an unpredictable sequence. For example,
reading var
from task1
can occur
either before or after writing to var
from task2
.
Therefore the value of var
can be unpredictable.
Though task3
calls increment
inside
a critical section, other tasks do not use the same critical section.
The operations in the critical section of task3
are
not mutually exclusive with operations in other tasks.
Therefore, the three tasks are operating on a shared variable without common protection. In your result details, you see each pair of conflicting function calls.
If you click the icon, you see the function call sequence starting
from the entry point to the read or write operation. You also see that the operation
starting from task3
is in a critical section. The Access
Protections entry shows the lock and unlock function that begin and end
the critical section. In this example, you see the functions
begin_critical_section
and
end_critical_section
.
One possible correction is to place the operation in critical section. You can implement the critical section in multiple ways. For instance:
You can place
var++
in a critical section. Whentask1
enters its critical section, the other tasks cannot enter their critical sections untiltask1
leaves its critical section. The operationvar++
from the three tasks cannot interfere with each other.To implement the critical section, in the function
increment
, place the operationvar++
between calls tobegin_critical_section
andend_critical_section
.int var; void begin_critical_section(void); void end_critical_section(void); void increment(void) { begin_critical_section(); var++; end_critical_section(); } void task1(void) { increment(); } void task2(void) { increment(); } void task3(void) { increment(); }
You can place the call to
increment
in the same critical section in the three tasks. Whentask1
enters its critical section, the other tasks cannot enter their critical sections untiltask1
leaves its critical section. The calls toincrement
from the three tasks cannot interfere with each other.To implement the critical section, in each of the three tasks, call
increment
between calls tobegin_critical_section
andend_critical_section
.int var; void begin_critical_section(void); void end_critical_section(void); void increment(void) { var++; } void task1(void) { begin_critical_section(); increment(); end_critical_section(); } void task2(void) { begin_critical_section(); increment(); end_critical_section(); } void task3(void) { begin_critical_section(); increment(); end_critical_section(); }
Another possible correction is to make the tasks, task1
, task2
and task3
,
temporally exclusive. Temporally exclusive tasks cannot execute concurrently.
On the Configuration pane, specify the following additional options:
Option | Value |
---|---|
Temporally exclusive tasks (-temporal-exclusions-file) |
|
On the command-line, you can use the following:
polyspace-bug-finder -temporal-exclusions-file "C:\exclusions_file.txt"
C:\exclusions_file.txt
has the following line:
task1 task2 task3
#include <pthread.h> pthread_mutex_t count_mutex; long long count; //Noncompliant void* increment_count(void* args) { count = count + 1; return NULL; } void* set_count(void *args) { long long c; c = count; return NULL; } int main(void) { pthread_t thread_increment; pthread_t thread_get; pthread_create(&thread_increment, NULL, increment_count, NULL); pthread_create(&thread_get, NULL, set_count, NULL); pthread_join(thread_get, NULL); pthread_join(thread_increment, NULL); return 1; }
In this example, Bug Finder detects
the creation of separate threads with pthread_create
. The Data
race defect is raised because the operation count = count +
1
in the thread with id thread_increment
conflicts with the
operation c = count
in the thread with id thread_get
.
The variable count
is accessed in multiple threads without a common
protection.
The two conflicting operations are
nonatomic. The operation c = count
is nonatomic on 32-bit targets. See
Define Atomic Operations in Multitasking Code.
pthread_mutex_lock
and
pthread_mutex_unlock
PairTo prevent concurrent access on the variable count
,
protect operations on count
with a critical
section. Use the functions pthread_mutex_lock
and
pthread_mutex_unlock
to implement the
critical section.
#include <pthread.h> pthread_mutex_t count_mutex; long long count; void* increment_count(void* args) { pthread_mutex_lock(&count_mutex); count = count + 1; pthread_mutex_unlock(&count_mutex); return NULL; } void* set_count(void *args) { long long c; pthread_mutex_lock(&count_mutex); c = count; pthread_mutex_unlock(&count_mutex); return NULL; } int main(void) { pthread_t thread_increment; pthread_t thread_get; pthread_create(&thread_increment, NULL, increment_count, NULL); pthread_create(&thread_get, NULL, set_count, NULL); pthread_join(thread_get, NULL); pthread_join(thread_increment, NULL); return 1; }
Data race on adjacent bit fields
This checker is deactivated in a default Polyspace as You Code analysis. See Checkers Deactivated in Polyspace as You Code Analysis (Polyspace Access).
This issue occurs when:
Multiple tasks perform unprotected operations on bit fields that are part of the same structure.
For instance, a task operates on field
errorFlag1
and another task on fielderrorFlag2
in a variable of this type:Suppose that the operations are not atomic with respect to each other. In other words, you have not implemented protection mechanisms to ensure that one operation is completed before another operation begins.struct errorFlags { unsigned int errorFlag1 : 1; unsigned int errorFlag2 : 1; ... }
At least one of the unprotected operations is a write operation.
To find this defect, before analysis, you must specify the multitasking options. To specify these options, on the Configuration pane, select Multitasking. For more information, see Configuring Polyspace Multitasking Analysis Manually.
Adjacent bit fields that are part of the same structure might be stored in one byte in the same memory location. Read or write operations on all variables including bit fields occur one byte or word at a time. To modify only specific bits in a byte, steps similar to these steps occur in sequence:
The byte is loaded into RAM.
A mask is created so that only specific bits are modified to the intended value and the remaining bits remain unchanged.
A bitwise OR operation is performed between the copy of the byte in RAM and the mask.
The byte with specific bits modified is copied back from RAM.
When you access two different bit fields, these four steps have to be performed for each
bit field. If the accesses are not protected, all four steps for one bit field might not be
completed before the four steps for the other bit field begin. As a result, the modification
of one bit field might undo the modification of an adjacent bit field. For instance, in the
preceding example, the modification of errorFlag1
and
errorFlag2
can occur in the following sequence. Steps 1,2 and 5 relate
to modification of errorFlag1
and while steps 3,4 and 6 relate to that of
errorFlag2
.
The byte with both
errorFlag1
anderrorFlag2
unmodified is copied into RAM, for purposes of modifyingerrorFlag1
.A mask that modifies only
errorFlag1
is bitwise OR-ed with this copy.The byte containing both
errorFlag1
anderrorFlag2
unmodified is copied into RAM a second time, for purposes of modifyingerrorFlag2
.A mask that modifies only
errorFlag2
is bitwise OR-ed with this second copy.The version with
errorFlag1
modified is copied back. This version haserrorFlag2
unmodified.The version with
errorFlag2
modified is copied back. This version haserrorFlag1
unmodified and overwrites the previous modification.
To fix this defect, protect the operations on bit fields that are part of the same structure by using critical sections, temporal exclusion, or another means. See Protections for Shared Variables in Multitasking Code.
To identify existing protections that you can reuse, see the table and graphs associated with the result. The table shows each pair of conflicting calls. The Access Protections column shows existing protections on the calls. To see the function call sequence leading to the conflicts, click the icon.
typedef struct { unsigned int IOFlag :1; unsigned int InterruptFlag :1; unsigned int Register1Flag :1; unsigned int SignFlag :1; unsigned int SetupFlag :1; unsigned int Register2Flag :1; unsigned int ProcessorFlag :1; unsigned int GeneralFlag :1; } InterruptConfigbits_t; InterruptConfigbits_t InterruptConfigbitsProc12; //Noncompliant void task1 (void) { InterruptConfigbitsProc12.IOFlag = 0; } void task2 (void) { InterruptConfigbitsProc12.SetupFlag = 0; }
In this example, task1
and task2
access different bit fields IOFlag
and SetupFlag
, which belong to the same structured variable InterruptConfigbitsProc12
.
To emulate multitasking behavior, specify the options listed in this table.
Option | Specification | |
---|---|---|
Configure multitasking manually | ||
Tasks |
|
At the command-line, use:
polyspace-bug-finder -entry-points task1,task2
One possible correction is to wrap the bit field access in a critical section. A critical
section lies between a call to a lock function and an unlock function. In this
correction, the critical section lies between the calls to functions
begin_critical_section
and
end_critical_section
.
typedef struct { unsigned int IOFlag :1; unsigned int InterruptFlag :1; unsigned int Register1Flag :1; unsigned int SignFlag :1; unsigned int SetupFlag :1; unsigned int Register2Flag :1; unsigned int ProcessorFlag :1; unsigned int GeneralFlag :1; } InterruptConfigbits_t; InterruptConfigbits_t InterruptConfigbitsProc12; void begin_critical_section(void); void end_critical_section(void); void task1 (void) { begin_critical_section(); InterruptConfigbitsProc12.IOFlag = 0; end_critical_section(); } void task2 (void) { begin_critical_section(); InterruptConfigbitsProc12.SetupFlag = 0; end_critical_section(); }
In this example, to emulate multitasking behavior, specify options listed in this table.
Option | Specification | |
---|---|---|
Configure multitasking manually | ||
Tasks |
| |
Critical section details | Starting routine | Ending routine |
begin_critical_section | end_critical_section |
At the command-line, use:
polyspace-bug-finder -entry-points task1,task2 -critical-section-begin begin_critical_section:cs1 -critical-section-end end_critical_section:cs1
If you do not have memory constraints, use the char
data type instead of
bit fields. The char
variables in a structure occupy at least one
byte and do not have the thread-safety issues that come from bit manipulations in a
byte-sized operation. Data races do not result from unprotected operations on different
char
variables that are part of the same structure.
typedef struct { unsigned char IOFlag; unsigned char InterruptFlag; unsigned char Register1Flag; unsigned char SignFlag; unsigned char SetupFlag; unsigned char Register2Flag; unsigned char ProcessorFlag; unsigned char GeneralFlag; } InterruptConfigbits_t; InterruptConfigbits_t InterruptConfigbitsProc12; void task1 (void) { InterruptConfigbitsProc12.IOFlag = 0; } void task2 (void) { InterruptConfigbitsProc12.SetupFlag = 0; }
Though the checker does not flag this correction, do not use this correction for C99 or
earlier. Only from C11 and later does the C Standard mandate that distinct
char
variables cannot be accessed using the same word.
You can enter a non-bit field member or an unnamed bit field member of size 0 between two
adjacent bit fields that might be accessed concurrently. A non-bit field member or size
0 bit field member ensures that the subsequent bit field starts from a new memory
location. In this corrected example, the size 0 bit field member ensures that
IOFlag
and SetupFlag
are stored in distinct
memory locations.
typedef struct { unsigned int IOFlag :1; unsigned int InterruptFlag :1; unsigned int Register1Flag :1; unsigned int SignFlag :1; unsigned int : 0; unsigned int SetupFlag :1; unsigned int Register2Flag :1; unsigned int ProcessorFlag :1; unsigned int GeneralFlag :1; } InterruptConfigbits_t; InterruptConfigbits_t InterruptConfigbitsProc12; void task1 (void) { InterruptConfigbitsProc12.IOFlag = 0; } void task2 (void) { InterruptConfigbitsProc12.SetupFlag = 0; }
Data race through standard library function call
This checker is deactivated in a default Polyspace as You Code analysis. See Checkers Deactivated in Polyspace as You Code Analysis (Polyspace Access).
This issue occurs when:
Multiple tasks call the same standard library function.
For instance, multiple tasks call the
strerror
function.The calls are not protected using a common protection.
For instance, the calls are not protected by the same critical section.
Functions flagged by this defect are not guaranteed to be reentrant. A function is reentrant if it can be interrupted and safely called again before its previous invocation completes execution. If a function is not reentrant, multiple tasks calling the function without protection can cause concurrency issues. For the list of functions that are flagged, see CON33-C: Avoid race conditions when using library functions.
To find this defect, you must specify the multitasking options before analysis. To specify these options, on the Configuration pane, select Multitasking. For more information, see Configuring Polyspace Multitasking Analysis Manually.
The functions flagged by this defect are nonreentrant because their implementations can use global or static variables. When multiple tasks call the function without protection, the function call from one task can interfere with the call from another task. The two invocations of the function can concurrently access the global or static variables and cause unpredictable results.
The calls can also cause more serious security vulnerabilities, such as abnormal termination, denial-of-service attack, and data integrity violations.
To fix this defect, do one of the following:
Use a reentrant version of the standard library function if it exists.
For instance, instead of
strerror()
, usestrerror_r()
orstrerror_s()
. For alternatives to functions flagged by this defect, see the documentation for CON33-C.Protect the function calls using common critical sections or temporal exclusion.
See
Critical section details (-critical-section-begin -critical-section-end)
andTemporally exclusive tasks (-temporal-exclusions-file)
.To identify existing protections that you can reuse, see the table and graphs associated with the result. The table shows each pair of conflicting calls. The Access Protections column shows existing protections on the calls. To see the function call sequence leading to the conflicts, click the icon. For an example, see below.
#include <errno.h> #include <stdio.h> #include <string.h> void begin_critical_section(void); void end_critical_section(void); FILE *getFilePointer(void); void func(FILE *fp) { fpos_t pos; errno = 0; if (0 != fgetpos(fp, &pos)) { char *errmsg = strerror(errno); //Noncompliant printf("Could not get the file position: %s\n", errmsg); } } void task1(void) { FILE* fptr1 = getFilePointer(); func(fptr1); } void task2(void) { FILE* fptr2 = getFilePointer(); func(fptr2); } void task3(void) { FILE* fptr3 = getFilePointer(); begin_critical_section(); func(fptr3); end_critical_section(); }
In this example, to emulate multitasking behavior, specify the following options:
Option | Specification | |
---|---|---|
Configure multitasking manually | ||
Tasks (-entry-points) |
| |
Critical section details (-critical-section-begin -critical-section-end) | Starting routine | Ending routine |
begin_critical_section | end_critical_section |
On the command-line, you can use the following:
polyspace-bug-finder -entry-points task1,task2,task3 -critical-section-begin begin_critical_section:cs1 -critical-section-end end_critical_section:cs1
In this example, the tasks, task1
, task2
and task3
,
call the function func
. func
calls
the nonreentrant standard library function, strerror
.
Though task3
calls func
inside
a critical section, other tasks do not use the same critical section.
Operations in the critical section of task3
are
not mutually exclusive with operations in other tasks.
These three tasks are calling a nonreentrant standard library function without common protection. In your result details, you see each pair of conflicting function calls.
If you click the icon, you see the function call sequence starting from the
entry point to the standard library function call. You also see that the call starting from
task3
is in a critical section. The Access Protections
entry shows the lock and unlock function that begin and end the critical section. In this
example, you see the functions begin_critical_section
and
end_critical_section
.
One possible correction is to use a reentrant version of the
standard library function strerror
. You can use
the POSIX version strerror_r
which has the
same functionality but also guarantees thread-safety.
#include <errno.h> #include <stdio.h> #include <string.h> void begin_critical_section(void); void end_critical_section(void); FILE *getFilePointer(void); enum { BUFFERSIZE = 64 }; void func(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); } } void task1(void) { FILE* fptr1 = getFilePointer(); func(fptr1); } void task2(void) { FILE* fptr2 = getFilePointer(); func(fptr2); } void task3(void) { FILE* fptr3 = getFilePointer(); begin_critical_section(); func(fptr3); end_critical_section(); }
One possible correction is to place the call to strerror
in
critical section. You can implement the critical section in multiple
ways.
For instance, you can place the call to the intermediate function func
in
the same critical section in the three tasks. When task1
enters
its critical section, the other tasks cannot enter their critical
sections until task1
leaves its critical section.
The calls to func
and therefore the calls to strerror
from
the three tasks cannot interfere with each other.
To implement the critical section, in each of the three tasks,
call func
between calls to begin_critical_section
and end_critical_section
.
#include <errno.h> #include <stdio.h> #include <string.h> void begin_critical_section(void); void end_critical_section(void); FILE *getFilePointer(void); void func(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); } } void task1(void) { FILE* fptr1 = getFilePointer(); begin_critical_section(); func(fptr1); end_critical_section(); } void task2(void) { FILE* fptr2 = getFilePointer(); begin_critical_section(); func(fptr2); end_critical_section(); } void task3(void) { FILE* fptr3 = getFilePointer(); begin_critical_section(); func(fptr3); end_critical_section(); }
Another possible correction is to make the tasks, task1
, task2
and task3
,
temporally exclusive. Temporally exclusive tasks cannot execute concurrently.
On the Configuration pane, specify the following additional options:
Option | Value |
---|---|
Temporally exclusive tasks (-temporal-exclusions-file) |
|
On the command-line, you can use the following:
polyspace-bug-finder -temporal-exclusions-file "C:\exclusions_file.txt"
C:\exclusions_file.txt
has the following line:
task1 task2 task3
Check Information
Category: Concurrency Issues |
Version History
Introduced in R2023a
See Also
External Websites
MATLAB Command
You clicked a link that corresponds to this MATLAB command:
Run the command by entering it in the MATLAB Command Window. Web browsers do not support MATLAB commands.
Select a Web Site
Choose a web site to get translated content where available and see local events and offers. Based on your location, we recommend that you select: .
You can also select a web site from the following list
How to Get Best Site Performance
Select the China site (in Chinese or English) for best site performance. Other MathWorks country sites are not optimized for visits from your location.
Americas
- América Latina (Español)
- Canada (English)
- United States (English)
Europe
- Belgium (English)
- Denmark (English)
- Deutschland (Deutsch)
- España (Español)
- Finland (English)
- France (Français)
- Ireland (English)
- Italia (Italiano)
- Luxembourg (English)
- Netherlands (English)
- Norway (English)
- Österreich (Deutsch)
- Portugal (English)
- Sweden (English)
- Switzerland
- United Kingdom (English)
Asia Pacific
- Australia (English)
- India (English)
- New Zealand (English)
- 中国
- 日本Japanese (日本語)
- 한국Korean (한국어)