Race condition

Published: Nov 3, 2015 by nemanjan00

Race condition is very common problem which is not mentioned so commonly and that is not so good.

Problem lays in pretty simple fact. Code is not executed in real-time.

Let’s say we have web store. When customer buys something, our code first checks if customer have enough money. After that, if customer have enough money, code checks if that item is currently available in store. If it is, then it takes amount it got while checking amount first time, subtract it with item price and update users credit.

[QUERY USERS CREDIT]
[CHECK IF CREDIT > COST]
[CHECK IF ITEM IS AVAILABLE]
[SUBTRACT USERS CREDIT FROM STEP 1]
[UPDATE USERS CREDIT]

At first, it all looks cool… But, wait a minute! What if user send two requests one after another?

[QUERY USERS CREDIT]
			[QUERY USERS CREDIT]
			[CHECK IF CREDIT > COST]
			[CHECK IF ITEM IS AVAILABLE]
			[SUBTRACT USERS CREDIT FROM STEP 1]
			[UPDATE USERS CREDIT]
[CHECK IF CREDIT > COST]
[CHECK IF ITEM IS AVAILABLE]
[SUBTRACT USERS CREDIT FROM STEP 1]
[UPDATE USERS CREDIT]

So, user just got two items for price of one. Not what we wanted, right?

Real-life demonstration

Just to prove how big of a problem this is, I created very simple demo.

This is file which is vulnerable:

<?php
// Lets just read number from file
$counter = file_get_contents("./counter.txt");

//Increment it
$counter = $counter + 1;

//Ok, now, let's do something slow, not like something in counter.txt can change while we are doing this. Or can it? 
$n = Array(); 

for($i = 0; $i < 1000; $i++){
	$n[] = rand(1, 1000);
}

//Write new number
file_put_contents("./counter.txt", $counter);

This is file we are executing to exploit vulnerability:

<?php
for($i = 0; $i < 100; $i++){
	//execute it and do not wait :)
	system("php test.php  > /dev/null 2>/dev/null &");
}

So, this is how it looks like. Instead of 100, it outputs 58.

╭─nemanjan00@nemanjan00-laptop  ~/race-condition ‹› ‹master› 
╰─$ cat counter.txt 
0
╭─nemanjan00@nemanjan00-laptop  ~/race-condition ‹› ‹master› 
╰─$ php index.php 
╭─nemanjan00@nemanjan00-laptop  ~/race-condition ‹› ‹master*› 
╰─$ cat counter.txt
58 

So, what is the solution?

If you are working with file, there is very simple solution. It is called flock. Basically, you lock file while you work with it and scripts works with it one by one.

<?php
$filename = "./counter.txt";

//Let's open file
$fp = fopen($filename, "r+");

//Let's lock file
while(!flock($fp, LOCK_EX));

//Let's read file content
$counter = fread($fp, filesize($filename));

//Increment it
$counter = $counter + 1;

echo $counter;

//Ok, now, let's do something slow, not like something in counter.txt can change while we are doing this. Or can it? 
$n = Array(); 

for($i = 0; $i < 1000; $i++){
	$n[] = rand(1, 1000);
}

//Write new number
ftruncate($fp, 0);
fseek($fp, 0);
fwrite($fp, $counter);

fflush($fp);
flock($fp, LOCK_UN);

fclose($fp);

Here is example executed:

╭─nemanjan00@nemanjan00-laptop  ~/race-condition ‹› ‹master*› 
╰─$ cat counter.txt     
0
╭─nemanjan00@nemanjan00-laptop  ~/race-condition ‹› ‹master*› 
╰─$ php index_secure.php
╭─nemanjan00@nemanjan00-laptop  ~/race-condition ‹› ‹master*› 
╰─$ cat counter.txt     
100

For database it is pretty similar process so I will not be describing it. If you need to find how to do it in other case, just google it.

Share

Latest Posts

How to print all rejections in NodeJS
How to print all rejections in NodeJS

Did you forget to print error on rejection, in a huge project and are now having issues debugging it? I wrote a snippet of code to solve that.

Why argv sucks for users
Why argv sucks for users

There are some conventions and patterns that are part of so much software we are never getting rid of them. One of those conventions defines how we pass arguments to applications.

Do not run chrome from your app!
Do not run chrome from your app!

This is example how your user can be hacked if you misuse chromium to open web links.