[libcxx-commits] [PATCH] D118134: [libc++] Fix TOCTOU issue with std::filesystem::remove_all
Arthur O'Dwyer via Phabricator via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Jan 25 08:37:32 PST 2022
Quuxplusone requested changes to this revision.
Quuxplusone added a comment.
This revision now requires changes to proceed.
Further discussion is on /r/cpp: https://old.reddit.com/r/cpp/comments/s8ok0h/possible_toctou_vulnerabilities_in/hti8jyt/
(Remember I told you Niall Douglas was once working on a thing for secure filesystem? There he is talking about it!)
================
Comment at: libcxx/include/__filesystem/directory_options.h:26
+ skip_permission_denied = 2,
+ __disallow_root_directory_symlink = 4
};
----------------
Please add a trailing comma here too, so you don't have to touch this line next time. :)
================
Comment at: libcxx/src/filesystem/directory_iterator.cpp:200
+ int fd = ::openat(AT_FDCWD, root.c_str(), O_CLOEXEC | O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+ __stream_ = ::fdopendir(fd);
+ } else {
----------------
Surely you should check `if (fd == -1)` (or even `if (fd < 0)`) here, and bail down to line 206 if needed.
================
Comment at: libcxx/src/filesystem/directory_iterator.cpp:207
ec = detail::capture_errno();
const bool allow_eacess =
bool(opts & directory_options::skip_permission_denied);
----------------
Pre-existing: `allow_eacces`
================
Comment at: libcxx/src/filesystem/operations.cpp:1377
+ for (; !ec && it != end; it.increment(ec)) {
+ count += remove_all_impl(it->path(), ec);
if (ec)
----------------
Doesn't this flatten/round-trip everything back through `path`, which means you have the symlink vulnerability //again// at this level?
Suppose I ask to `remove_all("/tmp/foo")`. The STL securely/atomically opens `/tmp/foo` as a directory (detecting and rejecting any attempt by me to `rm -rf /tmp/foo ; ln -sf /root /tmp/foo`). Then it starts iterating over that open directory. (At this point I can `ln -sf /root /tmp/foo` if I want, but it won't matter because the STL is already iterating over the //real// inode and no longer cares what's at that path in the filesystem.)
The STL removes `/tmp/foo/a.txt`. Then it sees a subdirectory `/tmp/foo/bin`. So it... uh... goes back to open //the path// `/tmp/foo/bin`?? But in the meantime, I have done `rm -rf /tmp/foo ; ln -sf /usr /tmp/foo`. So now when the STL opens //the path// `/tmp/foo/bin`, it's secretly opening `/usr/bin`, and will happily delete everything out of it. (Notice that `bin` there is not a symlink, so `O_NOFOLLOW` is happy.)
I believe that a proper fix for this issue requires using [`openat`](https://linux.die.net/man/2/openat) at every level. As soon as the code touches `fs::path`, it's game over. (Bonus: `fs::path` does a ton of heap allocation, but with `openat` I suspect you never need to allocate, do you?)
Repository:
rG LLVM Github Monorepo
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D118134/new/
https://reviews.llvm.org/D118134
More information about the libcxx-commits
mailing list