According to Advanced Programming in the UNIX Environment, openat()
provides a way to avoid time-of-check-to-time-of-use (TOCTTOU) errors.
I’m confused about how. As far as I know, openat
relies on a file descriptor, so this file descriptor needs to be opened first - does this introduce TOCTTOU?
The POSIX specification for openat()
says:
int openat(int
fd
, const char *
path
, int
oflag
, ...);
[…lengthy spiel on regular
open()
…]The
openat()
function shall be equivalent to theopen()
function except in the case wherepath
specifies a relative path. In this case the file to be opened is determined relative to the directory associated with the file descriptorfd
instead of the current working directory. If the file descriptor was opened withoutO_SEARCH
, the function shall check whether directory searches are permitted using the current permissions of the directory underlying the file descriptor. If the file descriptor was opened withO_SEARCH
, the function shall not perform the check.The
oflag
parameter and the optional fourth parameter correspond exactly to the parameters ofopen()
.If
openat()
is passed the special valueAT_FDCWD
in thefd
parameter, the current working directory shall be used and the behavior shall be identical to a call toopen()
.
This means that if you want to place files in, or relative to, a specific directory, you can open a file descriptor to that directory (probably with the O_SEARCH
option), and then specify path names relative to that directory in the openat()
system call.
Other *at()
functions such as fstatat()
work similarly.
First, note that the file descriptor is the file descriptor of a directory. At the time when the directory was opened (for reading), it existed, and the process had permission to access the directory and the files in it. Further, because this process has the directory open, the last references to that directory won't vanish until the process closes the directory file descriptor. If it's on a mounted file system, that file system can't be unmounted until the program terminates (because the process has the directory open). If the directory is moved (on the same file system), then files will continue to be created relative to that directory in its current position in the file system.
Things get more speculative from here on — I've not formally tested these observations.
Even if the directory was removed, it appears that you'd be still able to create files relative to it. If the names are simple names ("new_file"
or "./new_file"
), that should be OK. If the names have more of a path ("subdir/new_file"
), creating or opening the file will fail if the directory has been removed because all sub-directories will also have been removed.
Of course, there's mkdirat()
to create sub-directories.
Presumably, the file system has to clean up after all this, which could be quite complex. That means there's a chance that in fact you can't create files in a directory for which you have an open file descriptor but for which the name has been removed. However, you would know that this is no longer possible, rather than assuming that it is the same directory.
Either way, it is harder for the attacker to confuse your program into creating files in the wrong directory, as long as you've been careful and consistent in using the correct *at()
functions.
One set of TOCTOU attacks is removed; those attacks that depend on the directory being renamed (or possibly removed) and instead using a new name (e.g. a symlink to some other location) are foiled because the files continue to be created relative to the original directory, not using the renamed or replacement directory.
The rationale section of the POSIX specification for openat()
says:
The purpose of the
openat()
function is to enable opening files in directories other than the current working directory without exposure to race conditions. Any part of the path of a file could be changed in parallel to a call toopen()
, resulting in unspecified behavior. By opening a file descriptor for the target directory and using theopenat()
function it can be guaranteed that the opened file is located relative to the desired directory. Some implementations use theopenat()
function for other purposes as well. In some cases, if the oflag parameter has theO_XATTR
bit set, the returned file descriptor provides access to extended attributes. This functionality is not standardized here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With