Main Content

CWE Rule 413

Improper Resource Locking

Since R2023a

Description

Rule Description

The software does not lock or does not correctly lock a resource when the software must have exclusive access to the resource.

Polyspace Implementation

The rule checker checks for these issues:

  • Data race

  • Data race through standard library function call

  • Shared data access within signal handler

Examples

expand all

Issue

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:

  1. Multiple tasks perform unprotected operations on a shared variable.

  2. At least one task performs a write operation.

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

    See Define Atomic Operations in Multitasking Code.

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 find this defect, 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

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.
See also Filter and Group Results in Polyspace Desktop User Interface or Filter and Sort Results in Polyspace Access Web Interface (Polyspace Access).

Fix

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 Checker

Extend this checker to check for data races in operations that Bug Finder might not detect by default. For instance:

Example — Unprotected Operation on Global Variable from Multiple Tasks


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:

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

Correction — Place Operation in 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. When task1 enters its critical section, the other tasks cannot enter their critical sections until task1 leaves its critical section. The operation var++ from the three tasks cannot interfere with each other.

    To implement the critical section, in the function increment, place the operation var++ between calls to begin_critical_section and end_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. When task1 enters its critical section, the other tasks cannot enter their critical sections until task1 leaves its critical section. The calls to increment from the three tasks cannot interfere with each other.

    To implement the critical section, in each of the three tasks, call increment between calls to begin_critical_section and end_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();
    }

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

Example — Unprotected Operation in Threads Created with pthread_create
#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.

Correction — Protect Operations with pthread_mutex_lock and pthread_mutex_unlock Pair

To 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;
}
Issue

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:

  1. Multiple tasks call the same standard library function.

    For instance, multiple tasks call the strerror function.

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

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

Issue

This issue occurs when you access or modify a shared object inside a signal handler.

Risk

When you define a signal handler function to access or modify a shared object, the handler accesses or modifies the shared object when it receives a signal. If another function is already accessing the shared object, that function causes a race condition and can leave the data in an inconsistent state.

Fix

To access or modify shared objects inside a signal handler, check that the objects are lock-free atomic, or, if they are integers, declare them as volatile sig_atomic_t.

Example — int Variable Access in Signal Handler
#include <signal.h>
#include <stdlib.h>
#include <string.h>

/* declare global variable. */
int e_flag;

void sig_handler(int signum)
{
	/* Signal handler accesses variable that is not
	of type volatile sig_atomic_t. */
    e_flag = signum;  //Noncompliant
}

int func(void)
{
    if (signal(SIGINT, sig_handler) == SIG_ERR)
    {
        /* Handle error */
        abort();
    }
    /* Program code */
    if (raise(SIGINT) != 0)
    {
        /* Handle error */
        abort();
    }
    /* More code */
    return 0;
}
        
      

In this example, sig_handler accesses e_flag, a variable of type int. A concurrent access by another function can leave e_flag in an inconsistent state.

Correction — Declare Variable of Type volatile sig_atomic_t

Before you access a shared variable from a signal handler, declare the variable with type volatile sig_atomic_t instead of int. You can safely access variables of this type asynchronously.

#include <signal.h>
#include <stdlib.h>
#include <string.h>

/* Declare variable of type volatile sig_atomic_t. */
volatile sig_atomic_t e_flag;
void sig_handler(int signum)
{
	/* Use variable of proper type inside signal handler. */
    e_flag = signum;
    
}

int func(void)
{
    if (signal(SIGINT, sig_handler) == SIG_ERR)
    {
        /* Handle error */
        abort();
    }
    /* Program code */
    if (raise(SIGINT) != 0)
    {
        /* Handle error */
        abort();
    }
    /* More code */
    return 0;
} 

Check Information

Category: Resource Locking Problems

Version History

Introduced in R2023a