...
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 refers or path refers to the same file 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.
...
If an existing file is opened for writing with the w
mode argument, the file's previous contents are (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; if ((f = fopen(file, "r")); if (NULL != NULLf) { /* File exists, handle error */ } else { if (fclose(f) != 0) { /* handleHandle error */ } if ((f = fopen(file, "w")) == NULL; if (NULL != f) { /* Handle error */ } /* writeWrite to file */ if (fclose(f) != 0) { /* handleHandle error */ } } } |
Compliant Solution (C11 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 done without creating a race window. Note that the x
mode provides exclusive access to the file only if the operating platform provides the host environment provides this support.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h> void open_some_file(const char *file) { FILE *f; if ((f = fopen(file, "wx")) if (NULL != NULLf) { /* handleHandle error */ } /* writeWrite to file */ if (fclose(f) != 0) { /* handleHandle error */ } } |
Compliant Solution (POSIX write)
This compliant solution uses the O_CREAT
and O_EXCL
flags of POSIX's open()
system call 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 (fd-1 != -1fd) { FILE *f = fdopen(fd, "w"); if (fNULL != NULLf) { printf("access granted.\n"); /* writeWrite to file */ if (fclose(f) != 0) { /* handleHandle error */ } } if (close(fd) != 0) { /* handleHandle error */ } } } |
Noncompliant Code Example (POSIX read)
...
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h> #include <unistd.h> void open_some_file(const char *file) { FILE *f; if (access(file, R_OK) == 0) { printf("access granted.\n"); FILE *f = fopen(file, "rb"); if (fNULL == NULLf) { /* Handle error */ } /* readRead file */ if (fclose(f) != 0) { /* handleHandle error */ } } } |
Compliant Solution (read)
This compliant solution dispenses with an 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 (fNULL == NULLf) { /* Handle error */ } printf("access granted.\n"); /* readRead file */ if (fclose(f) != 0) { /* handleHandle error */ } } |
Exceptions
FIO45-EX1: Accessing a path 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 does or path does not violate this standard. Example programs would include file managers or text editors.
FIO45-EX2: Accessing a path multiple times is permitted if the path can not be modified by an attacker. This could occur, for example, if the path refers to a secure directory (for more information, see FIO15-C. Ensure that file operations are performed in a secure directory).
FIO45-EX3: Repeatedly opening and closing a file in append mode (in order to add data) is permitted as an exception to this rule. Many servers will open a log file for appending, write a few log messages, and immediately close the file. The server does not care if this log file changes periodically, and many systems archive old log files and create a new empty log file periodically.
...
This code example demonstrates how to verify that two accesses are indeed the same file in POSIX. In POSIX, every file can be uniquely identified by using its device and i-node attributes. This code example checks that a filename refers to a regular file (instead of a directory, symbolic link, etc). This is done using lstat()
; the call also retrieves its device and i-node. The file is subsequently opened. Finally, the program verifies that the file that was opened is the same one (matching device and inodesi-nodes) as the file that was confirmed as a regular file.
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) { /* fileFile does not exist, handle error */ } if (!S_ISREG(lstat_info.st_mode)) { /* fileFile is not a regular symlinkfile, handle error */ } if ((f = open(filename, flags)) == -1) { /* fileFile has disappeared, handle error */ } if (fstat(f, &fstat_info) == -1) { /* handleHandle error */ } if (lstat_info.st_ino != fstat_info.st_ino || lstat_info.st_dev != fstat_info.st_dev) { /* openOpen file is not non-symlink file, handle error */ } /* f is truethe expected open file, and file was a notregular symlinkfile */ return f; } |
Risk Assessment
...