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 Overflow
Top answer
1 of 1
29

A call to fopen is not in itself a TOCTOU vulnerability. By definition, TOCTOU involves two operations: a “check” and a “use”.

A common example of TOCTOU vulnerability is checking access permissions with access before opening a file. It's a bug (race condition) because the permissions might change between checking and opening, and it's usually a vulnerability because file permissions are important for security.

The access system call is highly suspicious because there aren't many ways to use it that don't introduce a TOCTOU. The fopen call in itself is not particularly suspicious because there are many ways to use it that are safe. However this doesn't mean that access is the only way to make a TOCTOU with fopen.

An example of race condition that can occur when opening files is if you got the file name from some external source, and you make assumptions about that name. For example, if you build the argument to fopen by concatenating a directory name with a file name, and you've made some checks on the directory, that's a TOCTOU vulnerability as well. The checks on the directory may no longer be valid by the time you use the directory to open a file. That's why Linux has the openat system call: so you can call opendir on a directory, perform checks on the directory, then call openat to open a file inside that directory (whereas open with concatenated names would open a file in a directory which now has this name, but may not be the one you checked).

So yes, you do need to care. Your code may or may not have a vulnerability. In my very limited experience, Klocwork does have a lot of false positives, but not everything is a false positive. Start by reading the complete message carefully. Review your code carefully, with written-down security objectives, and track how these security objectives are met (or not). There's no miracle: writing correct and secure code is harder than just applying a checklist.

Top answer
1 of 1
9

The reliable way to check whether the file exists and can be opened is to try opening it. If it was opened, all was OK. If it was not opened, you can think about spending time to analyze what went wrong.

The access() function formally asks a different question from what you think; it asks 'can the real user ID or the real group ID access the file', but the program will use the effective user ID or the effective group ID to access the file. If your program is not running SUID or SGID, and was not launched from a program running SUID or SGID — and that's the normal case — then there's no difference. But the question is different.

The use of stat() or lstat() doesn't seem helpful. In particular, lstat() only tells you whether you start at a symlink, but the code doesn't care about that.

Both the access() and the stat() calls provide you with TOCTOU windows of vulnerability; the file could be removed after they reported it was present, or created after they reported it was absent.

You should simply call fopen() and see whether it works; the code will be simpler and more resistant to TOCTOU problems. You might need to consider whether to use open() with all its extra controls (O_EXCL, etc), and then convert the file descriptor to a file pointer (fdopen()).

All of this applies to the Unix side.

The details will be different, but on the Windows side, you will still be best off trying to open the file and reacting appropriately to failure.

In both systems, make sure the options provided to the open function are appropriate.

🌐
SEI CERT
wiki.sei.cmu.edu › confluence › display › c › FIO45-C.+Avoid+TOCTOU+race+conditions+while+accessing+files
FIO45-C. Avoid TOCTOU race conditions while accessing files | CERT Secure Coding
If an existing file is opened for writing with the w mode argument, the file's previous contents (if any) are destroyed. This noncompliant code example tries to prevent an existing file from being overwritten by first opening it for reading before opening it for writing.
🌐
Open-std
open-std.org › jtc1 › sc22 › wg14 › www › docs › n1339.pdf pdf
#1 fopen() exclusive access with “x”
fp = fopen("foo.txt","w"); ... fclose(fp); } else { /* file exists */ fclose(fp); } ... However, this code suffers from a Time of Check, Time of Use (or TOCTOU) vulnerability. On a shared multitasking system there is a window of opportunity between · the first call of fopen() and the second ...
🌐
SEI CERT
wiki.sei.cmu.edu › confluence › x › RdUxBQ
FIO45-C. Avoid TOCTOU race conditions while accessing files - SEI CERT C Coding Standard - Confluence
If an existing file is opened for writing with the w mode argument, the file's previous contents (if any) are destroyed. This noncompliant code example tries to prevent an existing file from being overwritten by first opening it for reading before opening it for writing.
🌐
Wikipedia
en.wikipedia.org › wiki › Time-of-check_to_time-of-use
Time-of-check to time-of-use - Wikipedia
2 weeks ago - In software development, time-of-check to time-of-use (TOCTOU, TOCTTOU or TOC/TOU) is a class of software bugs caused by a race condition involving the checking of the state of a part of a system (such as a security credential) and the use of the results of that check.
🌐
GitHub
github.com › davidenetti › TOCTOU_Vulnerability
GitHub - davidenetti/TOCTOU_Vulnerability: PoC and notes about TOCTOU (race condition) vulnerability in C language and tested on GNU/Linux (Ubuntu 16.04). · GitHub
PoC and notes about TOCTOU (race condition) vulnerability in C language and tested on GNU/Linux (Ubuntu 16.04). This PoC tries to exploit a race condition present in a vulnerable program written in C ("vulp.c"). The race condition is located between the syscalls "access" and "fopen" (time of check/time of use) of the "vulp.c" file.
Author   davidenetti
🌐
50.005 CSE
natalieagus.github.io › 50005 › labs › 02-toctou
Time of Check, Time of Use Attack | 50.005 CSE
The time-of-check to time-of-use (often abbreviated asTOCTOU, TOCTTOU or TOC/TOU) is a class of software bug caused by a race condition involving: The checking of the state of a part of a system (such as this check in vulnerable_root_prog using access), ... The time of USE (actual usage of ...
Find elsewhere
🌐
Sonar
sonarsource.com › blog › winning-the-race-against-toctou-vulnerabilities
Winning the race against TOCTOU vulnerabilities in C & C++ | Sonar
October 7, 2020 - That's why we've introduced rule S5847, Accessing files should not introduce TOCTOU vulnerabilities, for C, C++, and Objective-C (more languages "soon"!) to detect TOCTOU vulnerabilities in your code. Here's an example from an internal test project: I've said this already, but it's worth pointing out again that even though the `fopen` is written right after the access check, it may not run right after in a multi-tasking environment, i.e.
🌐
Root-me
repository.root-me.org › Programmation › C - C++ › EN - Secure Coding in C and C++ Race Conditions.pdf pdf
© 2008 Carnegie Mellon University Secure Coding in C and C++ Race Conditions
Linux TOCTOU Example · #include <stdio.h> #include <unistd.h> int main(int argc, char *argv[]) { FILE *fd; if (access("/some_file", W_OK) == 0) { printf("access granted.\n"); fd = fopen("/some_file", "wb+"); /* write to the file */ fclose(fd); } . . . return 0; } The access() function is ·
🌐
Society for Information Reuse and Integration
sis.pitt.edu › jjoshi › courses › IS2620 › Spring07 › Lecture4.pdf pdf
1 Secure Coding in C and C++ Race conditions Lecture 4
z ToCToU race conditions · z Can occur during file I/O · z Forms a RW by first checking some race object · and then using it · Example · z Assume the program is running with an · effective UID of root · int main(int argc, char *argv[]) { FILE *fd; if (access(“/some_file”, W_OK) == 0) { printf("access granted.\n"); fd = fopen(“/some_file”, "wb+"); /* write to the file */ fclose(fd); } else { err(1, "ERROR"); } return 0; } Figure 7-1 ·
🌐
Klocwork
help.klocwork.com › 2024 › en-us › reference › sv.toctou.file_access.htm
SV.TOCTOU.FILE_ACCESS | Klocwork 2024.4
If an attacker is able to change the state of an object between the time of the check and the time it's used, there is potential for a TOCTOU error, which typically happens with shared resources such as files, memory, and variables in multi-threaded programs. The following functions are prone to this type of attack: acct · access · chmod · lchown · chown · fopen ·
🌐
Reddit
reddit.com › r/cpp › winning the race against toctou vulnerabilities in c & c++
r/cpp on Reddit: Winning the race against TOCTOU vulnerabilities in C & C++
October 7, 2020 - #include <stdio.h> void fopen_with_toctou(const char *file) { if (access(file, F_OK) == -1 && errno == ENOENT) { // the file doesn't exist // it is now created in order to write some data inside FILE *f = fopen(file, "w"); // Noncompliant: a race condition window exist from access() call to fopen() call calls if (NULL == f) { /* Handle error */ } if (fclose(f) == EOF) { /* Handle error */ } } }
🌐
GitHub
github.com › thesc1entist › elfread › issues › 3
Fix toctou vulnerability · Issue #3 · thesc1entist/elfread
November 10, 2021 - elfread/elfread.c Line 224 in 4e7e5a4 if ((stat(filename, &sb) == -1) || S_ISDIR(sb.st_mode)) Calling stat() before locking the file with fopen() is a security issue. Use fopen() first to lock the file then use fstat() instead of stat().
🌐
Infosec Institute
resources.infosecinstitute.com › race-condition-toctou-vulnerability-lab
Race condition (TOCTOU) vulnerability lab | Infosec
March 14, 2016 - The program is vulnerable to a race condition because of the time window between the check which represents in calling "access()" system call and the use which represents in calling "fopen()" system call, and we simulating, this time, window by delaying the execution using the for statement.
🌐
Blogger
insanecoding.blogspot.com › 2007 › 03 › file-descriptors-and-why-we-cant-use.html
Insane Coding: File Descriptors and why we can't use them
March 15, 2007 - One prevalent problem in programming is the TOCTOU (Time Of Check Time Of Use) race condition. Having a file descriptor to a file allows one to check or alter the status on it without worrying something changed in the mean time. Take the following example: FILE *fp = fopen("myfile.txt", "r+b") ...
🌐
Itecnote
itecnote.com › tecnote › stat-fstat-lstat-and-fopen-how-to-write-toctou-protected-system-independent-code
Stat(), fstat(), lstat(), and fopen(); how to write TOCTOU protected system independent code – iTecNote
· From what I've read, the code ... close the file. It seems the best way to do this is a call to lstat(), a call to fopen(), a call to fstat() (to rule out the TOCTOU), and then the operations and closing the file....