How to trigger a race condition?

Asked by At

I am researching about fuzzing approaches, and I want to be sure which approach is suitable for Race Condition problem. Therefor I have a question about race condition itself. Let's suppose we have a global variable and some threads have access to it without any restriction. How can we trigger the existing race condition? Is it enough to run just the function that uses the global variable with several threads? I mean just running the function will trigger race condition anyway?

Here, I put some code, and I know it has race condition problem. I want to know which inputs should give the functions to trigger the corresponding race condition problem.

#include<thread>
#include<vector>
#include<iostream>
#include<experimental/filesystem>
#include<Windows.h>
#include<atomic>

using namespace std;
namespace fs = experimental::filesystem;


volatile int totalSum;      
//atomic<int> totalSum;     
volatile int* numbersArray;

void threadProc(int startIndex, int endIndex)
{
    Sleep(300);

    for(int i = startIndex; i < endIndex; i++)
    {
        totalSum += numbersArray[i];
    }
}

void performAddition(int maxNum, int threadCount)
{
    totalSum = 0;

    numbersArray = new int[maxNum];

    for(int i = 0; i < maxNum; i++)
    {
        numbersArray[i] = i + 1;
    }

    int numbersPerThread = maxNum / threadCount;

    vector<thread> workerThreads;

    for(int i = 0; i < threadCount; i++)
    {
        int startIndex = i * numbersPerThread;
        int endIndex = startIndex + numbersPerThread;

        if (i == threadCount - 1)
            endIndex = maxNum;

        workerThreads.emplace_back(threadProc, startIndex, endIndex);
    }

    for(int i = 0; i < workerThreads.size(); i++)
    {
        workerThreads[i].join();
    }

    delete[] numbersArray;
}

void printUsage(char* progname)
{
    cout << "usage: " << fs::path(progname).filename() << " maxNum threadCount\t with 1<maxNum<=10000, 0<threadCount<=maxNum" << endl;
}

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        printUsage(argv[0]);
        return -1;
    }

    long int maxNum = strtol(argv[1], nullptr, 10);
    long int threadCount = strtol(argv[2], nullptr, 10);

    if(maxNum <= 1 || maxNum > 10000 || threadCount <= 0 || threadCount > maxNum)
    {
        printUsage(argv[0]);
        return -2;
    }

    performAddition(maxNum, threadCount);

    cout << "Result: " << totalSum << " (soll: " << (maxNum * (maxNum + 1))/2 << ")" << endl;
    return totalSum;
}

Thanks for your help

1 Answers

0
Witold Kaczurba On Best Solutions

There may be many cases of race conditions. One of example for your case:

one thread:

  • reads commonly accessible variable (1)
  • increments it (2)
  • sets the common member variable to resulting value (to 2)

second thread starts just after the first thread read the common value

  • it read the same value (1)
  • incremented the value it read. (2)
  • then writes the calculated value to common member variable at the same time as first one. (2)

As a result

  • the member value was incremented only by one (to value of 2) , but it should increment by two (to value of 3) since two threads were acting on it.

Testing race conditions:

  • for your purpose (in the above example) you can detect race condition when you get different result than expected.

Triggerring

  • if you may want the described situation always to happen for the purpose of - you will need to coordinate the work of two threads. This will allow you to do your testing
  • Nevertheless coordination of two threads will violate definition race condition if it is defined as: "A race condition or race hazard is the behavior of an electronics, software, or other system where the system's substantive behavior is dependent on the sequence or timing of other uncontrollable events.". So you need to know what you want, and in summary race condition is an unwanted behavior, that in your case you want to happen what can make sense for testing purpose.
  • If you are asking generally - when a race condition can occur - it depends on your software design (e.g you can have shared atomic integers which are ok to be used), hardware design (eg. variables stored in temporary registers) and generally luck.

Hope this helps, Witold