CWE-362

Concurrent Execution using Shared Resource with Improper Synchronization (‘Race Condition’). [Insufficient-Control-Flow-Management]

Required inputs: IR

The product contains a code sequence that can run concurrently with other code, and the code sequence requires temporary, exclusive access to a shared resource, but a timing window exists in which the shared resource can be modified by another code sequence that is operating concurrently.

This can have security implications when the expected synchronization is in security-critical code, such as recording whether a user is authenticated or modifying important state information that should not be influenced by an outsider.

A race condition occurs within concurrent environments, and is effectively a property of a code sequence. Depending on the context, a code sequence may be in the form of a function call, a small number of instructions, a series of program invocations, etc.

A race condition violates these properties, which are closely related:

  • Exclusivity - the code sequence is given exclusive access to the shared resource, i.e., no other code sequence can modify properties of the shared resource before the original sequence has completed execution.
  • Atomicity - the code sequence is behaviorally atomic, i.e., no other thread or process can concurrently execute the same sequence of instructions (or a subset) against the same resource.

A race condition exists when an "interfering code sequence" can still access the shared resource, violating exclusivity. Programmers may assume that certain code sequences execute too quickly to be affected by an interfering code sequence; when they are not, this violates atomicity. For example, the single "x++" statement may appear atomic at the code layer, but it is actually non-atomic at the instruction layer, since it involves a read (the original value of x), followed by a computation (x+1), followed by a write (save the result to x).

The interfering code sequence could be "trusted" or "untrusted." A trusted interfering code sequence occurs within the product; it cannot be modified by the attacker, and it can only be invoked indirectly. An untrusted interfering code sequence can be authored directly by the attacker, and typically it is external to the vulnerable product.

Demonstrative Examples
Example 1

This code could be used in an e-commerce application that supports transfers between accounts. It takes the total amount of the transfer, sends it to the new account, and deducts the amount from the original account.

Example Language:Perl (Unsupported language for documentation only)
    $transfer_amount = GetTransferAmount();
    $balance = GetBalanceFromDatabase();

    if ($transfer_amount < 0) {
        FatalError("Bad Transfer Amount");
    }
    $newbalance = $balance - $transfer_amount;
    if (($balance - $transfer_amount) < 0) {
        FatalError("Insufficient Funds");
    }
    SendNewBalanceToDatabase($newbalance);
    NotifyUser("Transfer of $transfer_amount succeeded.");
    NotifyUser("New balance: $newbalance");

A race condition could occur between the calls to GetBalanceFromDatabase() and SendNewBalanceToDatabase().

Suppose the balance is initially 100.00. An attack could be constructed as follows:

(attack code)
    Example Language:Other
    In the following pseudocode, the attacker makes two simultaneous calls of the program, CALLER-1 and CALLER-2. Both callers are for the same user account.
    CALLER-1 (the attacker) is associated with PROGRAM-1 (the instance that handles CALLER-1). CALLER-2 is associated with PROGRAM-2.
    CALLER-1 makes a transfer request of 80.00.
    PROGRAM-1 calls GetBalanceFromDatabase and sets $balance to 100.00
    PROGRAM-1 calculates $newbalance as 20.00, then calls SendNewBalanceToDatabase().
    Due to high server load, the PROGRAM-1 call to SendNewBalanceToDatabase() encounters a delay.
    CALLER-2 makes a transfer request of 1.00.
    PROGRAM-2 calls GetBalanceFromDatabase() and sets $balance to 100.00. This happens because the previous PROGRAM-1 request was not processed yet.
    PROGRAM-2 determines the new balance as 99.00.
    After the initial delay, PROGRAM-1 commits its balance to the database, setting it to 20.00.
    PROGRAM-2 sends a request to update the database, setting the balance to 99.00

At this stage, the attacker should have a balance of 19.00 (due to 81.00 worth of transfers), but the balance is 99.00, as recorded in the database.

To prevent this weakness, the programmer has several options, including using a lock to prevent multiple simultaneous requests to the web application, or using a synchronization mechanism that includes all the code between GetBalanceFromDatabase() and SendNewBalanceToDatabase().

Example 2

The following function attempts to acquire a lock in order to perform operations on a shared resource.

Example Language:C
    void f(pthread_mutex_t *mutex) {
        pthread_mutex_lock(mutex);

        /* access shared resource */


        pthread_mutex_unlock(mutex);
    }

However, the code does not check the value returned by pthread_mutex_lock() for errors. If pthread_mutex_lock() cannot acquire the mutex for any reason, the function may introduce a race condition into the program and result in undefined behavior.

In order to avoid data races, correctly written programs must check the result of thread synchronization functions and appropriately handle all errors, either by attempting to recover from them or reporting them to higher levels.

Example Language:C
    int f(pthread_mutex_t *mutex) {
        int result;

        result = pthread_mutex_lock(mutex);
        if (0 != result)
            return result;


        /* access shared resource */


        return pthread_mutex_unlock(mutex);
    }
Example 3

Suppose a processor's Memory Management Unit (MMU) has 5 other shadow MMUs to distribute its workload for its various cores. Each MMU has the start address and end address of "accessible" memory. Any time this accessible range changes (as per the processor's boot status), the main MMU sends an update message to all the shadow MMUs.

Suppose the interconnect fabric does not prioritize such "update" packets over other general traffic packets. This introduces a race condition. If an attacker can flood the target with enough messages so that some of those attack packets reach the target before the new access ranges gets updated, then the attacker can leverage this scenario.

Excerpts from CWE [https://cwe.mitre.org], Copyright (C) 2006-2026, the MITRE Corporation. See section 9.4. "3rd-Party Licenses" in the documentation for full details.

Possible Messages

Key

Text

Severity

Disabled

discarded_return_with_entity

Return value of function discarded.

None

False

unhandled_return_value

Return value of function call not properly checked.

None

False

Options

allow_assignment_to_globals

allow_assignment_to_globals : bool = False

Whether assignment to global / static variables should be allowed. If set to false, an error will be reported if the returned value is assigned to a global variable and any call is performed before checking the return (i.e., some other routine could access the return value before checking it).
 

allow_assignment_to_variables_with_pointers

allow_assignment_to_variables_with_pointers : bool = True

Whether assignment to variables of which the address has been taken somewhere should be allowed. If set to false, an error will be reported if the return value is assigned to such a variable, to ensure that the return value is checked locally, before any access from outside is possible.
 

allowed_functions

allowed_functions : set[bauhaus.analysis.config.FunctionName] = {'memcpy', 'memmove', 'memset', 'strcat', 'strcpy', 'strncat', 'strncpy'}

Calls to these functions are ignored.
 

check_operators

check_operators : bool = False

Also check operator calls. Unused return values of assignment operators are only reported if given in function style syntax.
 

functions

functions

Type: dict[bauhaus.analysis.config.QualifiedName, bauhaus.ir.common.algorithms.matchers.Matcher]

Default:

{
   'pthread_mutex_lock': <bauhaus.rules.axivion.expressions.calls.unhandled_return_value.BinaryRelationAnyMatcher object at 0x7f6f1b83a0b0>,
   'pthread_mutex_trylock': <bauhaus.rules.axivion.expressions.calls.unhandled_return_value.BinaryRelationAnyMatcher object at 0x7f6f1b83a0b0>
}
Allows to declare function names for which a check must exist. The check is expressed as an IR pattern.
 

known_check_functions

known_check_functions : set[bauhaus.analysis.config.FunctionName] = set()

Collection of functions which are known to test return values of functions under test.
 

report_references

report_references : bool = False

Report returned references. For operators, unused returned references are only reported if given in function style syntax.