Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can openat avoid TOCTTOU errors?

Tags:

unix

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?

like image 941
Sherwin Avatar asked Apr 19 '16 03:04

Sherwin


1 Answers

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 the open() function except in the case where path specifies a relative path. In this case the file to be opened is determined relative to the directory associated with the file descriptor fd instead of the current working directory. If the file descriptor was opened without O_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 with O_SEARCH, the function shall not perform the check.

The oflag parameter and the optional fourth parameter correspond exactly to the parameters of open().

If openat() is passed the special value AT_FDCWD in the fd parameter, the current working directory shall be used and the behavior shall be identical to a call to open().

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.

  • How does this improve security?

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 to open(), resulting in unspecified behavior. By opening a file descriptor for the target directory and using the openat() function it can be guaranteed that the opened file is located relative to the desired directory. Some implementations use the openat() function for other purposes as well. In some cases, if the oflag parameter has the O_XATTR bit set, the returned file descriptor provides access to extended attributes. This functionality is not standardized here.

like image 114
Jonathan Leffler Avatar answered Sep 17 '22 17:09

Jonathan Leffler