Main Content

CWE Rule 765

Multiple Unlocks of a Critical Resource

Since R2024a

Description

Rule Description

The product unlocks a critical resource more times than intended, leading to an unexpected state in the system.

Polyspace Implementation

The rule checker checks for Double unlock.

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:

  • A task calls a lock function my_lock.

  • The task calls the corresponding unlock function my_unlock.

  • The task calls my_unlock again. The task does not call my_lock a second time between the two calls to my_unlock.

In multitasking code, a lock function begins a critical section of code and an unlock function ends it. When a task task1 calls a lock function my_lock, other tasks calling my_lock must wait until task1 calls the corresponding unlock function.

To find this defect, specify your lock and unlock functions using one of these methods:

Risk

A double unlock defect can indicate a coding error. Perhaps you wanted to call a different unlock function to end a different critical section. Perhaps you called the unlock function prematurely the first time and only the second call indicates the end of the critical section.

Fix

The fix depends on the root cause of the defect.

Identify each critical section of code, that is, the section that you want to be executed as an atomic block. Call a lock function at the beginning of the section. Only at the end of the section, call the unlock function that corresponds to the lock function. Remove any other redundant call to the unlock function.

See examples of fixes below. To avoid the issue, you can follow the practice of calling the lock and unlock functions in the same module at the same level of abstraction. For instance, in this example, func calls the lock and unlock function at the same level but func2 does not.

void func() {
  my_lock();
  {
    ...
  }
  my_unlock();
}

void func2() {
  {
   my_lock();
   ...
  }
  my_unlock();
}

If you do not want to fix the issue, add comments to your result or code to avoid another review. See:

Extend Checker

You might be using unlocking functions that are not supported by Polyspace. Extend this checker by mapping your unlocking functions to its known POSIX® equivalent. See Extend Concurrency Defect Checkers to Unsupported Multithreading Environments.

Example — Double Unlock


int global_var;

void BEGIN_CRITICAL_SECTION(void);
void END_CRITICAL_SECTION(void);

void task1(void)
{
    BEGIN_CRITICAL_SECTION();
    global_var += 1;
    END_CRITICAL_SECTION();
    global_var += 1;
    END_CRITICAL_SECTION(); //Noncompliant
}

void task2(void)
{
    BEGIN_CRITICAL_SECTION();
    global_var += 1;
    END_CRITICAL_SECTION();
}

In this example, to emulate multitasking behavior, you must specify the following options:

OptionValue
Configure multitasking manually
Tasks (-entry-points)

task1

task2

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
   -critical-section-begin BEGIN_CRITICAL_SECTION:cs1
   -critical-section-end END_CRITICAL_SECTION:cs1

task1 enters a critical section through the call BEGIN_CRITICAL_SECTION();. task1 leaves the critical section through the call END_CRITICAL_SECTION();. task1 calls END_CRITICAL_SECTION again without an intermediate call to BEGIN_CRITICAL_SECTION.

Correction — Remove Second Unlock

If you want the second global_var+=1; to be outside the critical section, one possible correction is to remove the second call to END_CRITICAL_SECTION. However, if other tasks are using global_var, this code can produce a Data race error.



int global_var;

void BEGIN_CRITICAL_SECTION(void);
void END_CRITICAL_SECTION(void);

void task1(void)
{
    BEGIN_CRITICAL_SECTION();
    global_var += 1;
    END_CRITICAL_SECTION();
    global_var += 1;
}

void task2(void)
{
    BEGIN_CRITICAL_SECTION();
    global_var += 1;
    END_CRITICAL_SECTION();
}
Correction — Remove First Unlock

If you want the second global_var+=1; to be inside the critical section, one possible correction is to remove the first call to END_CRITICAL_SECTION.



int global_var;

void BEGIN_CRITICAL_SECTION(void);
void END_CRITICAL_SECTION(void);

void task1(void)
{
    BEGIN_CRITICAL_SECTION();
    global_var += 1;
    global_var += 1;
    END_CRITICAL_SECTION();
}

void task2(void)
{
    BEGIN_CRITICAL_SECTION();
    global_var += 1;
    END_CRITICAL_SECTION();
}
Correction — Add Another Lock

If you want the second global_var+=1; to be inside a critical section, another possible correction is to add another call to BEGIN_CRITICAL_SECTION.



int global_var;

void BEGIN_CRITICAL_SECTION(void);
void END_CRITICAL_SECTION(void);

void task1(void)
{
    BEGIN_CRITICAL_SECTION();
    global_var += 1;
    END_CRITICAL_SECTION();
    BEGIN_CRITICAL_SECTION();
    global_var += 1;
    END_CRITICAL_SECTION();
}

void task2(void)
{
    BEGIN_CRITICAL_SECTION();
    global_var += 1;
    END_CRITICAL_SECTION();
}

Check Information

Category: Resource Locking Problems

Version History

Introduced in R2024a