Detecting Time of Check Time of Use Bugs With Coderrect

Prerequisites

This tutorial assumes you have successfully installed the Coderrect software following the quick start.


Background

Time of check time of use, or TOCTOU, is a type of software bug that can lead to serious security vulnerabilities. At the time of writing, searching the keyword “TOCTOU” in the Common Vulnerabilities Database returns 94 cases where a TOCTOU bug could be exploited maliciously. These cases show examples of arbitrary code execution, privilege escalation, unintended file deletion, and many more types of exploits in widely used software. For a full description of TOCTOU, check out this blog post.

A simple example of a multi-threaded TOCTOU race is shown blow.

// toctou_example.cpp
#include <pthread.h>
#include <iostream>

int *global;
pthread_mutex_t lock;

void *worker1(void *arg) {
    pthread_mutex_lock(&lock);
    global = nullptr;
    std::cout << (global);
    pthread_mutex_unlock(&lock);
}

void *worker2(void *arg) {
    if (global != nullptr) {
        // TOCTOU if worker1 interleaves here
        pthread_mutex_lock(&lock);
        std::cout << *global << "\n";
        pthread_mutex_unlock(&lock);
    }
}

int main() {
    pthread_t th1, th2;
    global = new int(0);
    pthread_mutex_init(&lock, nullptr);

    pthread_create(&th1, nullptr, worker1, nullptr);
    pthread_create(&th2, nullptr, worker2, nullptr);

    pthread_join(th1, nullptr);
    pthread_join(th2, nullptr);
}

The program spawns two threads; worker1 and worker2. The second thread, worker2 attempts to dereference a global pointer. However, the programmer remembered that the first thread, worker1, may set the value of the global pointer to null in parallel. So, the programmer added a check to ensure that the global pointer is not null to avoid a potential null pointer dereference.

Unfortunately, there is a TOCTOU race in this code. It is possible for worker1 to assign global to be null after worker2‘s if condition, but before the dereference.


Detect the race using the Coderrect Scanner

The Coderrect Scanner automatically flags TOCTOU races at the top of the report. There is no special flags or configurations required to detect TOCTOU races. Simply run coderrect the same way it would be run to detect other race conditions.

coderrect -t g++ toctou_example.cpp -lpthread

The -t flag indicates that coderrect should print a report to the terminal for easy viewing. The remaining arguments are the command used to build the toctou_example.cpp code. Coderrect successfully detects the TOCTOU race. A snippet of the terminal report is shown below.

==== Found a TOCTOU between: 
line 9, column 5 in toctou_example.cpp AND line 15, column 9 in toctou_example.cpp
Shared variable:
 at line 4 of toctou_example.cpp
 4|int *global;
Thread 1:
 7|void *worker1(void *arg) {
 8|    pthread_mutex_lock(&lock);
>9|    global = nullptr;
 10|    std::cout << (global);
 11|    pthread_mutex_unlock(&lock);
>>>Stacktrace:
>>>pthread_create [toctou_example.cpp:28]
>>>  worker1(void*) [toctou_example.cpp:28]
Thread 2:
 13|
 14|void *worker2(void *arg) {
>15|    if (global != nullptr) {
 16|        // TOCTOU if worker1 interleaves here
 17|        pthread_mutex_lock(&lock);
>>>Stacktrace:
>>>pthread_create [toctou_example.cpp:29]
>>>  worker2(void*) [toctou_example.cpp:29]

Interpret the Results

The TOCTOU report should be familiar if you have used coderrect to detect data races before. The report is in the same format as the data race report, showing the shared variable on which the race occurs, the source lines that cause the race, and the calling stack trace showing how each thread arrives at the racing locations.

Terminal Report

Just as for data races, the terminal report first shows the two code locations that lead to the TOCTOU race.

==== Found a TOCTOU between: 
line 9, column 5 in toctou_example.cpp AND line 15, column 9 in toctou_example.cpp

Next a shared variable is shown. This is the variable that is updated in parallel and cases the “check” to be invalidated before the “use”.

Shared variable:
 at line 4 of toctou_example.cpp
 4|int *global;

Then a short code snippet showing the racing lines is printed for both threads. The code snippet for Thread 1 is shown below.

Thread 1:
 7|void *worker1(void *arg) {
 8|    pthread_mutex_lock(&lock);
>9|    global = nullptr;
 10|    std::cout << (global);
 11|    pthread_mutex_unlock(&lock);

Lastly, a stack trace is reported for each thread. The stack trace shows the chain of functions that are called to arrive at the racing location in the code. The stack trace for thread 1 is shown below.

>>>Stacktrace:
>>>pthread_create [toctou_example.cpp:28]
>>>  worker1(void*) [toctou_example.cpp:28]

The above stack trace shows that thread 1 is created by a call to pthread_create in toctou_example.cpp at line 28 and then thread one begins executing the function worker1 which is where the racing access occurs.

All together the full report shows that the worker2 function has a branch whose condition depends on the value of the shared global variable and the function worker1 can update that variable in parallel, leading to a potential TOCTOU race.

HTML Report

The terminal is great to get a quick idea about what races are reported, but the full report can be viewed in the browser.

When we initially ran coderrect we used the -t flag to print the results to the terminal Coderrect can also generate an html report to be viewed in the browser with the following syntax:

coderrect -o report g++ toctou_example.cpp -lpthread

This will create a directory named report and a file named index.html within that directory. To view the full report open the index.html file in a browser.