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;
}
Answer from Toby Speight on Stack OverflowNo, 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;
}
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.