Main Content

CERT C++: CON33-C

Avoid race conditions when using library functions

Description

This checker is deactivated in a default Polyspace® as You Code analysis. See Checkers Deactivated in Polyspace as You Code Analysis (Polyspace Access).

Rule Definition

Avoid race conditions when using library functions.1

Polyspace Implementation

The rule checker checks for Data race through standard library function call.

Extend Checker

For the rule checker to detect issues, your code must use one of the concurrency primitives recognized by Polyspace for thread creation and shared variable protection. Otherwise, you must explicitly specify tasks, interrupts, critical sections, and other multitasking options in your project configuration. See Multitasking.

Examples

expand all

Issue

Data 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.

Risk

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.

Fix

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(), 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:

OptionSpecification
Configure multitasking manually
Tasks (-entry-points)

task1

task2

task3

Critical section details (-critical-section-begin -critical-section-end)Starting routineEnding routine
begin_critical_sectionend_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.

A snapshot of the Result Details pane showing calls to a standard library function from several pairs of threads or tasks. The Access Protections column shows whether the calls are protected from concurrent access.

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.

A pictorial view of calls to a standard library function from a pair of threads or tasks.

Correction — Use Reentrant Version of Standard Library Function

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();
}
Correction — Place Function Call in 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();
}

Correction — Make Tasks Temporally Exclusive

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:

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:
task1 task2 task3

Check Information

Group: 10. Concurrency (CON)

Version History

Introduced in R2019a


1 This software has been created by MathWorks incorporating portions of: the “SEI CERT-C Website,” © 2017 Carnegie Mellon University, the SEI CERT-C++ Web site © 2017 Carnegie Mellon University, ”SEI CERT C Coding Standard – Rules for Developing safe, Reliable and Secure systems – 2016 Edition,” © 2016 Carnegie Mellon University, and “SEI CERT C++ Coding Standard – Rules for Developing safe, Reliable and Secure systems in C++ – 2016 Edition” © 2016 Carnegie Mellon University, with special permission from its Software Engineering Institute.

ANY MATERIAL OF CARNEGIE MELLON UNIVERSITY AND/OR ITS SOFTWARE ENGINEERING INSTITUTE CONTAINED HEREIN IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.

This software and associated documentation has not been reviewed nor is it endorsed by Carnegie Mellon University or its Software Engineering Institute.