Introduction

TOCTTOU

Execution ordering is only guaranteed within a thread. There are many possible execution orderings for a "simultaneous" launch of two processes P1P_1 and P2P_2.

Some execution orderings can be buggy. For example, the program calls two functions do_action() and check_input(). If the execution ordering is different from the intended logic that the programmer expected, the attacker may bypass the check and trick the program to execute something that it is not supposed to execute. This bug type is called Time of Check to Time of Use, and it is abbreviated as TOCTTOU.

Example

Consider the following source code toctou.c:

// toctou.c
#include <assert.h>
#include <unistd.h>
#include <stdio.h>

void check_input(char *filename)
{
    int i;
    FILE *f = fopen(filename, "r");
    fscanf(f, "%d", &i);
    fclose(f);
    assert(i == 0);
}

void do_action(char *filename)
{
    int i;
    FILE *f = fopen(filename, "r");
    fscanf(f, "%d", &i);
    fclose(f);

    i++;
    f = fopen(filename, "w");
    fprintf(f, "%d\n", i);
    printf("Wrote %d!\n", i);
    fclose(f);
}

int main(int argc, char **argv)
{
    check_input(argv[1]);
    do_action(argv[1]);
}

Compile the source code:

gcc toctou.c -o toctou

Now let's do an experiment. Open tmux and create 3 windows. In the first two windows (processes P1P_1 and P2P_2), start two infinite loops that keep executing toctou:

while : ; do ./toctou num ; done 2>/dev/null

In the last window, start another infinite loop that keeps writing 0 to num:

while : ; do echo 0 > num ; done

Here we can notice the inconsist results:

The intended output is Wrote 1!, but there are cases where the program outputs Wrote 2!. This is becasue we trigger the TOCTOU bug. For these cases, the execution ordering is something like this:

P1: check_input();
P2: check_input();
P1: do_action();
P2: do_action();

Both processes call check_input() first, so they pass the assert(i == 0); check. Later on, both processes call do_action() so that the counter i gets incremented twice. As a result, we have i = 2 in the end.

Last updated