Using stat() after fopen() to Avoid TOCTOU Problems?
Asked Answered
M

2

9

Title says it all: can one use stat() after fopen() to avoid Time of Check to Time of Use (TOCTOU) race conditions?

Some details:

I am writing a C program that only reads files, but needs to error properly when asked to read a directory. As of right now, it uses open() (with O_RDWR) to generate an error and then checks errno for EISDIR, like so:

int fd = open(path, O_RDWR);

if (fd == -1) {
    if (errno == EISDIR) return PATH_IS_DIR;
    else return FILE_ERR;
}

The problem with the above solution is that this program only needs to read files, so by opening a file with O_RDWR, I might wrongly get a permissions error if the user has read permissions, but not write permissions.

Is it possible to do the following to avoid TOCTOU race conditions?

struct stat pstat;

FILE *f = fopen(path, "r");

if (!f) return FILE_ERR;

if (stat(path, &pstat) == -1) {
    fclose(f);
    return FILE_ERR;
}

if (S_ISDIR(pstat.st_mode)) {
    fclose(f);
    return PATH_IS_DIR;
}

If it is not possible, is there another solution to prevent TOCTOU bugs and also wrong permission errors?

Mainly answered 23/10, 2018 at 15:26 Comment(0)
M
10

No, the code presented in the question does not avoid a TOCTOU race.

Testing after use is prone to exactly the same errors as testing before use. In both cases, the name is resolved at two different times, with possibly different results. This is the cause of the race, and it can happen whichever access happens first.

The only way to avoid this is to open the file once, and use the file descriptor so obtained for any other checks you need. Modern OSes provide interfaces such as fstat() for exactly this purpose.

If you want to use C's buffered I/O, you can get the file descriptor from a FILE* using fileno() - or you can create a FILE* from a file descriptor using fdopen().

It requires a very small change to your code:

# Untested

struct stat pstat;

FILE *f = fopen(path, "r");

if (!f) return FILE_ERR;

if (fstat(fileno(f), &pstat) == -1) {
//  ^^^^^^^^^^^^^^^                         <-- CHANGED HERE
    fclose(f);
    return FILE_ERR;
}

if (S_ISDIR(pstat.st_mode)) {
    fclose(f);
    return PATH_IS_DIR;
}
Makeyevka answered 24/10, 2018 at 16:37 Comment(2)
I like this one better. Thank you. Accepted.Mainly
For anyone in the future who wishes to go the route of fopen/fstat: if((stream = fopen("input.dat","r+")) != NULL) { if((status = fstat(fileno(stream),&buffer)) == 0) { printf("file status obtained.\n"); printf("file size is %ld\n",buffer.st_size); } }Inessive
M
6

EDIT (2018-10-25): Toby Speight's answer is better.

There is a solution: use open(), then fstat().

An example:

struct stat pstat;

int fd = open(path, O_RDONLY);

if (fd == -1) return FILE_ERR;

if (fstat(fd, &pstat) == -1) {
    close(fd);
    return FILE_ERR;
}

if (S_ISDIR(pstat.st_mode)) {
    close(fd);
    return PATH_IS_DIR;
}

I found this while checking that I had covered all of my bases before asking this question.

Mainly answered 23/10, 2018 at 15:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.