A TOCTOU (time-of-check, time-of-use) race condition occurs is possible when a program performs two or more accesses on a filename or path. are more concurrent processes are operating on a shared file system [Seacord 2013]. Typically, the first access is a check to verify some attribute of the file, followed by a call to use the file. An attacker can alter the file between the two accesses, causing the check to succeed but the use to fail. Worse, the use can operate on a different file than the checkor replace the file with a symbolic or hard link to a different file. These TOCTOU conditions are frequently indicated when a program performs two or more file operations on the same filename or pathname.
A program that performs a file operation on a filename or path twice creates a race window between the two file operations. This race window comes from the assumption that the filename or path refers to the same resource both times. If an attacker can modify the file, remove it, or replace it with a different file, then this assumption will not hold.
Noncompliant Code Example (write)
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 trying to open it for reading before opening it for writing. An attacker can exploit the race window between the two calls to fopen()
to overwrite an existing file.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h> void open_some_file(const char *file) { FILE *f = fopen(file, "r"); if (NULL != f) { /* File exists, handle error */ } else { if (fclose(f) == EOF) { /* Handle error */ } f = fopen(file, "w"); if (NULL == f) { /* Handle error */ } /* Write to file */ if (fclose(f) == EOF) { /* Handle error */ } } } |
Compliant Solution (write)
This compliant solution uses only one instance of fopen()
, and uses the x
mode of fopen()
, which was added in C11. This mode causes fopen()
to fail if the file exists. This check and subsequent open is performed without creating a race window. Note that the x
mode provides exclusive access to the file only if the host environment provides this support.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h> void open_some_file(const char *file) { FILE *f = fopen(file, "wx") if (NULL == f) { /* Handle error */ } /* Write to file */ if (fclose(f) == EOF) { /* Handle error */ } } |
Compliant Solution (POSIX write)
This compliant solution uses the O_CREAT
and O_EXCL
flags of POSIX's open()
function. These flags cause open()
to fail if the file exists.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h> #include <unistd.h> #include <fcntl.h> void open_some_file(const char *file) { int fd = open(file, O_CREAT | O_EXCL | O_WRONLY); if (-1 != fd) { FILE *f = fdopen(fd, "w"); if (NULL != f) { /* Write to file */ if (fclose(f) == EOF) { /* Handle error */ } } if (close(fd) == -1) { /* Handle error */ } } } |
Noncompliant Code Example (POSIX read)
This noncompliant code example attempts to ensure that an open will succeed by first calling the POSIX access()
function, which returns zero if the file exists and can be accessed using the specified permissions. An attacker can exploit the race window between the access and the open to cause fopen()
to fail in spite of the check.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h> #include <unistd.h> void open_some_file(const char *file) { if (access(file, R_OK) == 0) { FILE *f = fopen(file, "rb"); if (NULL == f) { /* Handle error */ } /* Read file */ if (fclose(f) == EOF) { /* Handle error */ } } } |
Compliant Solution (read)
This compliant solution dispenses with an access()
call and merely relies on fopen()
to verify the file.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h> void open_some_file(const char *file) { FILE *f = fopen(file, "rb"); if (NULL == f) { /* Handle error */ } /* Read file */ if (fclose(f) == EOF) { /* Handle error */ } } |
Exceptions
FIO45-EX1: Accessing a filename or path multiple times is permitted if it is requested specifically by a user. A program that accepts commands from a user to read or write to a specific filename or path does not violate this standard. Example programs would include file managers or text editors.
...
Code Block | ||||
---|---|---|---|---|
| ||||
#include <sys/stat.h> #include <fcntl.h> int open_regular_file(char *filename, int flags) { struct stat lstat_info; struct stat fstat_info; int f; if (lstat(filename, &lstat_info) == -1) { /* File does not exist, handle error */ } if (!S_ISREG(lstat_info.st_mode)) { /* File is not a regular file, handle error */ } if ((f = open(filename, flags)) == -1) { /* File has disappeared, handle error */ } if (fstat(f, &fstat_info) == -1) { /* Handle error */ } if (lstat_info.st_ino != fstat_info.st_ino || lstat_info.st_dev != fstat_info.st_dev) { /* Open file is not the expected regular file, handle error */ } /* f is the expected regular open file */ return f; } |
Risk Assessment
TOCTOU race conditions can result in unexpected behavior, including privilege escalation.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
FIO45-C | High | Probable | High | P6 | L2 |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Bibliography
[Seacord 2013] | Chapter 7, "Files" |
...