public inbox for gcc-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc r11-11036] libstdc++: Avoid symlink race in filesystem::remove_all [PR104161]
@ 2023-10-04 11:28 Jonathan Wakely
0 siblings, 0 replies; only message in thread
From: Jonathan Wakely @ 2023-10-04 11:28 UTC (permalink / raw)
To: gcc-cvs, libstdc++-cvs
https://gcc.gnu.org/g:e20b1c711f70d8d251d45694c50c02e66a4d9f7b
commit r11-11036-ge20b1c711f70d8d251d45694c50c02e66a4d9f7b
Author: Jonathan Wakely <jwakely@redhat.com>
Date: Sun Jan 23 21:45:16 2022 +0000
libstdc++: Avoid symlink race in filesystem::remove_all [PR104161]
This adds a new internal flag to the filesystem::directory_iterator
constructor that makes it fail if the path is a symlink that resolves to
a directory. This prevents filesystem::remove_all from following a
symlink to a directory, rather than deleting the symlink itself.
We can also use that new flag in recursive_directory_iterator to ensure
that we don't follow symlinks if the follow_directory_symlink option is
not set.
This also moves an error check in filesystem::remove_all after the while
loop, so that errors from the directory_iterator constructor are
reproted, instead of continuing to the filesystem::remove call below.
libstdc++-v3/ChangeLog:
PR libstdc++/104161
* acinclude.m4 (GLIBCXX_CHECK_FILESYSTEM_DEPS): Check for
fdopendir.
* config.h.in: Regenerate.
* configure: Regenerate.
* src/c++17/fs_dir.cc (_Dir): Add nofollow flag to constructor
and pass it to base class constructor.
(directory_iterator): Pass nofollow flag to _Dir constructor.
(fs::recursive_directory_iterator::increment): Likewise.
* src/c++17/fs_ops.cc (do_remove_all): Use nofollow option for
directory_iterator constructor. Move error check outside loop.
* src/filesystem/dir-common.h (_Dir_base): Add nofollow flag to
constructor and when it's set use ::open with O_NOFOLLOW and
O_DIRECTORY.
* src/filesystem/dir.cc (_Dir): Add nofollow flag to constructor
and pass it to base class constructor.
(directory_iterator): Pass nofollow flag to _Dir constructor.
(fs::recursive_directory_iterator::increment): Likewise.
* src/filesystem/ops.cc (remove_all): Use nofollow option for
directory_iterator constructor. Move error check outside loop.
(cherry picked from commit c8bd4dc8212e43b2f9af08b80df97f90cdb0df4f)
Diff:
---
libstdc++-v3/acinclude.m4 | 12 +++++++
libstdc++-v3/config.h.in | 3 ++
libstdc++-v3/configure | 55 ++++++++++++++++++++++++++++++++
libstdc++-v3/src/c++17/fs_dir.cc | 13 +++++---
libstdc++-v3/src/c++17/fs_ops.cc | 12 +++----
libstdc++-v3/src/filesystem/dir-common.h | 48 +++++++++++++++++++++-------
libstdc++-v3/src/filesystem/dir.cc | 13 +++++---
libstdc++-v3/src/filesystem/ops.cc | 6 ++--
8 files changed, 134 insertions(+), 28 deletions(-)
diff --git a/libstdc++-v3/acinclude.m4 b/libstdc++-v3/acinclude.m4
index 15c975ff86e..d7d49b02e66 100644
--- a/libstdc++-v3/acinclude.m4
+++ b/libstdc++-v3/acinclude.m4
@@ -4825,6 +4825,18 @@ dnl
AC_DEFINE(HAVE_TRUNCATE, 1, [Define if truncate is available in <unistd.h>.])
fi
AC_MSG_RESULT($glibcxx_cv_truncate)
+dnl
+ AC_CACHE_CHECK([for fdopendir],
+ glibcxx_cv_fdopendir, [dnl
+ GCC_TRY_COMPILE_OR_LINK(
+ [#include <dirent.h>],
+ [::fdopendir(1);],
+ [glibcxx_cv_fdopendir=yes],
+ [glibcxx_cv_fdopendir=no])
+ ])
+ if test $glibcxx_cv_truncate = yes; then
+ AC_DEFINE(HAVE_FDOPENDIR, 1, [Define if fdopendir is available in <dirent.h>.])
+ fi
dnl
CXXFLAGS="$ac_save_CXXFLAGS"
AC_LANG_RESTORE
diff --git a/libstdc++-v3/config.h.in b/libstdc++-v3/config.h.in
index ea88281f438..9145c154e86 100644
--- a/libstdc++-v3/config.h.in
+++ b/libstdc++-v3/config.h.in
@@ -93,6 +93,9 @@
/* Define to 1 if you have the <fcntl.h> header file. */
#undef HAVE_FCNTL_H
+/* Define if fdopendir is available in <dirent.h>. */
+#undef HAVE_FDOPENDIR
+
/* Define to 1 if you have the <fenv.h> header file. */
#undef HAVE_FENV_H
diff --git a/libstdc++-v3/configure b/libstdc++-v3/configure
index 1d5ffd8df11..6ff56b7d2d7 100755
--- a/libstdc++-v3/configure
+++ b/libstdc++-v3/configure
@@ -76785,6 +76785,61 @@ $as_echo "#define HAVE_TRUNCATE 1" >>confdefs.h
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $glibcxx_cv_truncate" >&5
$as_echo "$glibcxx_cv_truncate" >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for fdopendir" >&5
+$as_echo_n "checking for fdopendir... " >&6; }
+if ${glibcxx_cv_fdopendir+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test x$gcc_no_link = xyes; then
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <dirent.h>
+int
+main ()
+{
+::fdopendir(1);
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+ glibcxx_cv_fdopendir=yes
+else
+ glibcxx_cv_fdopendir=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+else
+ if test x$gcc_no_link = xyes; then
+ as_fn_error $? "Link tests are not allowed after GCC_NO_EXECUTABLES." "$LINENO" 5
+fi
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <dirent.h>
+int
+main ()
+{
+::fdopendir(1);
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_link "$LINENO"; then :
+ glibcxx_cv_fdopendir=yes
+else
+ glibcxx_cv_fdopendir=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $glibcxx_cv_fdopendir" >&5
+$as_echo "$glibcxx_cv_fdopendir" >&6; }
+ if test $glibcxx_cv_truncate = yes; then
+
+$as_echo "#define HAVE_FDOPENDIR 1" >>confdefs.h
+
+ fi
CXXFLAGS="$ac_save_CXXFLAGS"
ac_ext=c
ac_cpp='$CPP $CPPFLAGS'
diff --git a/libstdc++-v3/src/c++17/fs_dir.cc b/libstdc++-v3/src/c++17/fs_dir.cc
index 054d3908991..efd5151d32a 100644
--- a/libstdc++-v3/src/c++17/fs_dir.cc
+++ b/libstdc++-v3/src/c++17/fs_dir.cc
@@ -44,8 +44,9 @@ template class std::__shared_ptr<fs::recursive_directory_iterator::_Dir_stack>;
struct fs::_Dir : _Dir_base
{
- _Dir(const fs::path& p, bool skip_permission_denied, error_code& ec)
- : _Dir_base(p.c_str(), skip_permission_denied, ec)
+ _Dir(const fs::path& p, bool skip_permission_denied, bool nofollow,
+ error_code& ec)
+ : _Dir_base(p.c_str(), skip_permission_denied, nofollow, ec)
{
if (!ec)
path = p;
@@ -128,11 +129,15 @@ namespace
fs::directory_iterator::
directory_iterator(const path& p, directory_options options, error_code* ecptr)
{
+ // Do not report an error for permission denied errors.
const bool skip_permission_denied
= is_set(options, directory_options::skip_permission_denied);
+ // Do not allow opening a symlink (used by filesystem::remove_all)
+ const bool nofollow
+ = is_set(options, __directory_iterator_nofollow);
error_code ec;
- _Dir dir(p, skip_permission_denied, ec);
+ _Dir dir(p, skip_permission_denied, nofollow, ec);
if (dir.dirp)
{
@@ -287,7 +292,7 @@ fs::recursive_directory_iterator::increment(error_code& ec)
if (std::exchange(_M_dirs->pending, true) && top.should_recurse(follow, ec))
{
- _Dir dir(top.entry.path(), skip_permission_denied, ec);
+ _Dir dir(top.entry.path(), skip_permission_denied, !follow, ec);
if (ec)
{
_M_dirs.reset();
diff --git a/libstdc++-v3/src/c++17/fs_ops.cc b/libstdc++-v3/src/c++17/fs_ops.cc
index 1fb295498c6..03d0da7d87c 100644
--- a/libstdc++-v3/src/c++17/fs_ops.cc
+++ b/libstdc++-v3/src/c++17/fs_ops.cc
@@ -1340,7 +1340,7 @@ namespace
uintmax_t count = 0;
if (s.type() == file_type::directory)
{
- directory_iterator d(p, ec), end;
+ directory_iterator d(p, directory_options{99}, ec), end;
while (d != end)
{
const auto removed = fs::do_remove_all(d->path(), err);
@@ -1349,11 +1349,11 @@ namespace
count += removed;
d.increment(ec);
- if (ec)
- {
- err.report(ec, p);
- return -1;
- }
+ }
+ if (ec)
+ {
+ err.report(ec, p);
+ return -1;
}
}
diff --git a/libstdc++-v3/src/filesystem/dir-common.h b/libstdc++-v3/src/filesystem/dir-common.h
index a49b8304a29..d90ff8938ca 100644
--- a/libstdc++-v3/src/filesystem/dir-common.h
+++ b/libstdc++-v3/src/filesystem/dir-common.h
@@ -36,6 +36,10 @@
# endif
# include <dirent.h>
#endif
+#ifdef _GLIBCXX_HAVE_FCNTL_H
+# include <fcntl.h> // O_NOFOLLOW, O_DIRECTORY
+# include <unistd.h> // close
+#endif
namespace std _GLIBCXX_VISIBILITY(default)
{
@@ -76,21 +80,40 @@ struct _Dir_base
_Dir_base(posix::DIR* dirp = nullptr) : dirp(dirp) { }
// If no error occurs then dirp is non-null,
- // otherwise null (whether error ignored or not).
+ // otherwise null (even if an EACCES error is ignored).
_Dir_base(const posix::char_type* pathname, bool skip_permission_denied,
- error_code& ec) noexcept
- : dirp(posix::opendir(pathname))
+ [[maybe_unused]] bool nofollow, error_code& ec) noexcept
+ : dirp(nullptr)
{
- if (dirp)
+#if defined O_RDONLY && O_NOFOLLOW && defined O_DIRECTORY && defined O_CLOEXEC \
+ && defined _GLIBCXX_HAVE_FDOPENDIR && !_GLIBCXX_FILESYSTEM_IS_WINDOWS
+ if (nofollow)
+ {
+ // Do not allow opening a symlink (used by filesystem::remove_all)
+ const int flags = O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC;
+ int fd = ::open(pathname, flags);
+ if (fd != -1)
+ {
+ if ((dirp = ::fdopendir(fd)))
+ {
+ ec.clear();
+ return;
+ }
+ }
+ if (errno == EACCES && skip_permission_denied)
+ ec.clear();
+ else
+ ec.assign(errno, std::generic_category());
+ return;
+ }
+#endif
+
+ if ((dirp = posix::opendir(pathname)))
+ ec.clear();
+ else if (errno == EACCES && skip_permission_denied)
ec.clear();
else
- {
- const int err = errno;
- if (err == EACCES && skip_permission_denied)
- ec.clear();
- else
- ec.assign(err, std::generic_category());
- }
+ ec.assign(errno, std::generic_category());
}
_Dir_base(_Dir_base&& d) : dirp(std::exchange(d.dirp, nullptr)) { }
@@ -187,6 +210,9 @@ get_file_type(const std::filesystem::__gnu_posix::dirent& d [[gnu::unused]])
return file_type::none;
#endif
}
+
+constexpr directory_options __directory_iterator_nofollow{99};
+
_GLIBCXX_END_NAMESPACE_FILESYSTEM
_GLIBCXX_END_NAMESPACE_VERSION
diff --git a/libstdc++-v3/src/filesystem/dir.cc b/libstdc++-v3/src/filesystem/dir.cc
index 446ddf0d2f2..d9c15aa9c34 100644
--- a/libstdc++-v3/src/filesystem/dir.cc
+++ b/libstdc++-v3/src/filesystem/dir.cc
@@ -51,8 +51,9 @@ namespace posix = std::filesystem::__gnu_posix;
struct fs::_Dir : std::filesystem::_Dir_base
{
- _Dir(const fs::path& p, bool skip_permission_denied, error_code& ec)
- : _Dir_base(p.c_str(), skip_permission_denied, ec)
+ _Dir(const fs::path& p, bool skip_permission_denied, bool nofollow,
+ error_code& ec)
+ : _Dir_base(p.c_str(), skip_permission_denied, nofollow, ec)
{
if (!ec)
path = p;
@@ -130,11 +131,15 @@ namespace
fs::directory_iterator::
directory_iterator(const path& p, directory_options options, error_code* ecptr)
{
+ // Do not report an error for permission denied errors.
const bool skip_permission_denied
= is_set(options, directory_options::skip_permission_denied);
+ // Do not allow opening a symlink (used by filesystem::remove_all)
+ const bool nofollow
+ = is_set(options, __directory_iterator_nofollow);
error_code ec;
- _Dir dir(p, skip_permission_denied, ec);
+ _Dir dir(p, skip_permission_denied, nofollow, ec);
if (dir.dirp)
{
@@ -274,7 +279,7 @@ fs::recursive_directory_iterator::increment(error_code& ec) noexcept
if (std::exchange(_M_pending, true) && top.should_recurse(follow, ec))
{
- _Dir dir(top.entry.path(), skip_permission_denied, ec);
+ _Dir dir(top.entry.path(), skip_permission_denied, !follow, ec);
if (ec)
{
_M_dirs.reset();
diff --git a/libstdc++-v3/src/filesystem/ops.cc b/libstdc++-v3/src/filesystem/ops.cc
index 33b8206b7d8..22f17630470 100644
--- a/libstdc++-v3/src/filesystem/ops.cc
+++ b/libstdc++-v3/src/filesystem/ops.cc
@@ -1113,7 +1113,7 @@ fs::remove_all(const path& p, error_code& ec)
uintmax_t count = 0;
if (s.type() == file_type::directory)
{
- directory_iterator d(p, ec), end;
+ directory_iterator d(p, directory_options{99}, ec), end;
while (!ec && d != end)
{
const auto removed = fs::remove_all(d->path(), ec);
@@ -1121,9 +1121,9 @@ fs::remove_all(const path& p, error_code& ec)
return -1;
count += removed;
d.increment(ec);
- if (ec)
- return -1;
}
+ if (ec)
+ return -1;
}
if (fs::remove(p, ec))
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2023-10-04 11:28 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-10-04 11:28 [gcc r11-11036] libstdc++: Avoid symlink race in filesystem::remove_all [PR104161] Jonathan Wakely
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).