The POSIX specification for openat() says:
int openat(int fd , const char * path , int oflag , ...);
[... long spiel on regular open() ...]
The openat() function should be equivalent to the open() function, unless path indicates a relative path. In this case, the file to be opened is determined relative to the directory associated with the file descriptor fd , and not with the current working directory. If the file descriptor was opened without O_SEARCH , the function should check if directory searches are allowed using the current permissions of the directory underlying the file descriptor. If the file descriptor was opened using O_SEARCH , the function should not perform the check.
The oflag parameter and optional fourth parameter exactly match the open() parameters.
If openat() passed the special value AT_FDCWD in the fd parameter, the current working directory should be used, and the behavior should be identical to calling open() .
This means that if you want to place files in a specific directory or in relation to it, you can open a file descriptor in that directory (possibly with the O_SEARCH parameter), and then specify the path names relative to this directory in the openat() system call.
Other *at() functions such as fstatat() work similarly.
- How does it improve security?
First, note that the file descriptor is a directory file descriptor. While the directory was open (for reading), it existed, and the process had permission to access the directory and the files in it. In addition, since this process has a directory open, the last links to this directory will not disappear until the process completes the directory file descriptor. If it is on a mounted file system, this file system cannot be unmounted until the program terminates (since the process has an open directory). If the directory is moved (in the same file system), then the files will continue to be created relative to this directory at its current position in the file system.
Things are becoming more speculative from here - I have not officially tested these observations.
Even if the directory has been deleted, it looks like you can still create files relative to it. If the names are simple names ( "new_file" or "./new_file" ), this should be OK. If the names have more than the path ( "subdir/new_file" ), creating or opening the file will fail if the directory has been deleted, since all subdirectories will also be deleted.
Of course there mkdirat() for creating subdirectories.
Presumably, the file system should clean up after all of this, which can be quite complex. This means that in fact you cannot create files in a directory for which you have an open file descriptor, but for which the name has been deleted. However, you know that this is no longer possible, and not assume that it is the same directory.
In any case, it is more difficult for an attacker to confuse your program with creating files in the wrong directory if you were careful and consistent in using the correct *at() functions.
One set of TOCTOU attacks is deleted; those attacks that depend on the renamed (or possibly deleted) directory instead use a new name (for example, a symbolic link to some other place), because the files continue to be created compared to the original directory, not using a renamed or replacement directory.
The rationale section of the POSIX specification for openat() says:
The purpose of the openat() function is to allow opening files in directories other than the current working directory without affecting race conditions. Any part of the file path can be changed in parallel with the open() call, which will lead to unspecified behavior. By opening the file descriptor for the target directory and using the openat() function, you can ensure that the open file is located relative to the desired directory. Some implementations use the openat() function for other purposes. In some cases, if the oflag parameter has the O_XATTR bit, the returned file descriptor provides access to extended attributes. This functionality is not standardized here.