Bug 197778

Summary: Implement the AT_EMPTY_PATH race free Linux extension
Product: Base System Reporter: Niall Douglas <s_bugzilla>
Component: kernAssignee: freebsd-bugs (Nobody) <bugs>
Status: Closed FIXED    
Severity: Affects Many People CC: crest, crest, emaste, trasz, val
Priority: ---    
Version: CURRENT   
Hardware: Any   
OS: Any   
See Also: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=197695
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198570
Bug Depends on:    
Bug Blocks: 247219    

Description Niall Douglas 2015-02-18 02:07:57 UTC
Related: #197695

Due to only accepting paths, POSIX makes it hard to unlink and rename files without creating race conditions. For example to unlink a file as safely as is possible under POSIX:

1. Get one of the current paths of the open file descriptor using any facility provided if #197695 were implement.

2. Open its containing directory.

3. Do a fstatat() on the containing directory for the leafname of the open file descriptor, checking if the device ids and inodes match the ones for our file descriptor.

4. If they match, do an unlinkat() to remove the leafname. NOTE THIS IS RACY as another program could swap our leafname for another between the fstatat and the unlinkat.

This raciness is disagreeable in this modern day and age. Moreover, Linux has partially added an extension via the AT_EMPTY_PATH flag which enables the XXXat() suite of functions work directly with the file descriptor. At the time of writing (Feb 2015), Linux implements the AT_EMPTY_PATH extension for:

linkat(), fchownat(), fstatat(), name_to_handle_at().

The AT_EMPTY_PATH extension works as follows, so to create a new hard link for some file descriptor:

linkat(fd, "", dirh, "name", AT_EMPTY_PATH)

Note the empty path, and the passing in of a file descriptor which may or may not refer to a directory as the olddirh fd normally must.

There are some obviously useful missing functions which could do with the AT_EMPTY_PATH extension:

unlinkat(), renameat().

I have filed a feature request for those with the Linux kernel at https://bugzilla.kernel.org/show_bug.cgi?id=93441. Note that because Linux allows the direct opening of symlinks via the flag O_PATH, on Linux you can use linkat() to duplicate a symlink race free. As FreeBSD doesn't provide the ability to directly open symlinks, I'd actually make the total list of functions which this request asks for the AT_EMPTY_PATH extension upon to be:

linkat(), fchownat(), fstatat(), unlinkat(), renameat(), symlinkat(), faccessat(), fchmodat().

Of these, renameat() and symlinkat() currently do not take a flags parameter. You could varargs those, or else enable the above functions to treat a null path fragment as equivalent to the dirh being the source filing system entry being modified i.e. a null path fragment = AT_EMPTY_PATH semantics.

Or indeed additional f* variants of the non-at functions could be added which consume file descriptors instead of paths e.g. the oft wished for flink() (more discussion is at http://lwn.net/Articles/562488/). The point here is making it possible to use the filing system without any possibility of race conditions, something only Microsoft Windows currently permits.

Some may observe that all this is a potential security hole e.g. child processes supposedly sandboxed could unlink or cause mischief with file descriptors they inherit, or otherwise destroy data. A similar argument could be fielded against #197695, except that every major OS except for FreeBSD has provided fd to path reading for years and I am unaware of any security issues which have emerged. For obvious reasons any of the above functions should fail if their path based equivalent would fail from the privileges available to a child process.

Niall
Comment 1 Edward Tomasz Napierala freebsd_committer freebsd_triage 2020-10-14 16:10:40 UTC
FWIW, this might be what's breaking "debootstrap sid".
Comment 2 Val Packett 2020-11-08 23:52:10 UTC
This flag is used in https://github.com/flatpak/xdg-desktop-portal 's document-portal, for fstatat in combination with AT_SYMLINK_NOFOLLOW, which seems to be "fstat the symlink". Since we do not have O_PATH, we can't get the same symlink handling as Linux at all, so this call in particular wouldn't be useful without O_PATH.
Comment 3 Edward Tomasz Napierala freebsd_committer freebsd_triage 2021-07-26 14:57:37 UTC
AT_EMPTY_PATH was committed by kib@ in 509124b6261 and 5e7cdf18179; O_PATH in 8d9ed174f3a.