bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] fchmodat, lchmod: port to buggy Linux filesystems


From: Paul Eggert
Subject: [PATCH] fchmodat, lchmod: port to buggy Linux filesystems
Date: Thu, 13 Feb 2020 10:42:09 -0800

Problem reported by Florian Weimer in:
https://www.sourceware.org/ml/libc-alpha/2020-02/msg00534.html
* lib/fchmodat.c (fchmodat):
* lib/lchmod.c (lchmod):
Don’t assume that chmod on the O_PATH-opened fd will do
the right thing on a symbolic link.
* lib/fchmodat.c (fchmodat):
Don’t attempt to special-case
any flag value other than AT_SYMLINK_NOFOLLOW.
---
 ChangeLog      | 13 +++++++++++++
 lib/fchmodat.c | 33 ++++++++++++++++++++++++++-------
 lib/lchmod.c   | 27 ++++++++++++++++++++++-----
 3 files changed, 61 insertions(+), 12 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index b5ef9b3a1..8bcf1bf0a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2020-02-13  Paul Eggert  <address@hidden>
+
+       fchmodat, lchmod: port to buggy Linux filesystems
+       Problem reported by Florian Weimer in:
+       https://www.sourceware.org/ml/libc-alpha/2020-02/msg00534.html
+       * lib/fchmodat.c (fchmodat):
+       * lib/lchmod.c (lchmod):
+       Don’t assume that chmod on the O_PATH-opened fd will do
+       the right thing on a symbolic link.
+       * lib/fchmodat.c (fchmodat):
+       Don’t attempt to special-case
+       any flag value other than AT_SYMLINK_NOFOLLOW.
+
 2020-02-11  Paul Eggert  <address@hidden>
 
        lchmod: pacify Coverity CID 1491216
diff --git a/lib/fchmodat.c b/lib/fchmodat.c
index 87aa0d191..02e2da956 100644
--- a/lib/fchmodat.c
+++ b/lib/fchmodat.c
@@ -63,12 +63,31 @@ orig_fchmodat (int dir, char const *file, mode_t mode, int 
flags)
 int
 fchmodat (int dir, char const *file, mode_t mode, int flags)
 {
-  if (flags & AT_SYMLINK_NOFOLLOW)
+  if (flags == AT_SYMLINK_NOFOLLOW)
     {
-# ifdef O_PATH
+      struct stat st;
+
+# if defined O_PATH && defined AT_EMPTY_PATH
       int fd = openat (dir, file, O_PATH | O_NOFOLLOW | O_CLOEXEC);
       if (fd < 0)
         return fd;
+
+      /* Use fstatat because fstat does not work on O_PATH descriptors
+         before Linux 3.6.  */
+      if (fstatat (fd, "", &st, AT_EMPTY_PATH) != 0)
+        {
+          int stat_errno = errno;
+          close (fd);
+          errno = stat_errno;
+          return -1;
+        }
+      if (S_ISLNK (st.st_mode))
+        {
+          close (fd);
+          errno = EOPNOTSUPP;
+          return -1;
+        }
+
       static char const fmt[] = "/proc/self/fd/%d";
       char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)];
       sprintf (buf, fmt, fd);
@@ -82,10 +101,8 @@ fchmodat (int dir, char const *file, mode_t mode, int flags)
           errno = chmod_errno;
           return chmod_result;
         }
-      /* /proc is not mounted; fall back on racy implementation.  */
-# endif
-
-      struct stat st;
+      /* /proc is not mounted.  */
+# else
       int fstatat_result = fstatat (dir, file, &st, AT_SYMLINK_NOFOLLOW);
       if (fstatat_result != 0)
         return fstatat_result;
@@ -94,7 +111,9 @@ fchmodat (int dir, char const *file, mode_t mode, int flags)
           errno = EOPNOTSUPP;
           return -1;
         }
-      flags &= ~AT_SYMLINK_NOFOLLOW;
+# endif
+      /* Fall back on chmod, despite the race.  */
+      flags = 0;
     }
 
   return orig_fchmodat (dir, file, mode, flags);
diff --git a/lib/lchmod.c b/lib/lchmod.c
index 5fc658023..c7191c07d 100644
--- a/lib/lchmod.c
+++ b/lib/lchmod.c
@@ -37,10 +37,28 @@ lchmod (char const *file, mode_t mode)
 #if HAVE_FCHMODAT
   return fchmodat (AT_FDCWD, file, mode, AT_SYMLINK_NOFOLLOW);
 #else
-# if defined O_PATH && defined AT_FDCWD
+# if defined AT_FDCWD && defined O_PATH && defined AT_EMPTY_PATH
   int fd = openat (AT_FDCWD, file, O_PATH | O_NOFOLLOW | O_CLOEXEC);
   if (fd < 0)
     return fd;
+
+  /* Use fstatat because fstat does not work on O_PATH descriptors
+     before Linux 3.6.  */
+  struct stat st;
+  if (fstatat (fd, "", &st, AT_EMPTY_PATH) != 0)
+    {
+      int stat_errno = errno;
+      close (fd);
+      errno = stat_errno;
+      return -1;
+    }
+  if (S_ISLNK (st.st_mode))
+    {
+      close (fd);
+      errno = EOPNOTSUPP;
+      return -1;
+    }
+
   static char const fmt[] = "/proc/self/fd/%d";
   char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)];
   sprintf (buf, fmt, fd);
@@ -54,10 +72,8 @@ lchmod (char const *file, mode_t mode)
       errno = chmod_errno;
       return chmod_result;
     }
-  /* /proc is not mounted; fall back on racy implementation.  */
-# endif
-
-# if HAVE_LSTAT
+  /* /proc is not mounted.  */
+# elif HAVE_LSTAT
   struct stat st;
   int lstat_result = lstat (file, &st);
   if (lstat_result != 0)
@@ -69,6 +85,7 @@ lchmod (char const *file, mode_t mode)
     }
 # endif
 
+  /* Fall back on chmod, despite the race.  */
   return chmod (file, mode);
 #endif
 }
-- 
2.24.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]