IssueData race through standard library
function call 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.
RiskThe 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.
FixTo 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()
, use strerror_r()
or
strerror_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)
and Temporally 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.
Example - Unprotected Call to Standard Library Function from Multiple
Tasks#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:
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
.
Correction — Use Reentrant Version of Standard Library
FunctionOne 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();
}
Correction — Place Function Call in Critical SectionOne 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();
}
Correction — Make Tasks Temporally ExclusiveAnother 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:
On the command-line, you can use the following:
polyspace-bug-finder
-temporal-exclusions-file "C:\exclusions_file.txt"
where the file
C:\exclusions_file.txt
has the following line: