]> Savannah Git Hosting - gnulib.git/commitdiff
fchmodat, lchmod: port to buggy Linux filesystems
authorPaul Eggert <eggert@cs.ucla.edu>
Thu, 13 Feb 2020 18:41:10 +0000 (10:41 -0800)
committerPaul Eggert <eggert@cs.ucla.edu>
Thu, 13 Feb 2020 18:41:54 +0000 (10:41 -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
lib/fchmodat.c
lib/lchmod.c

index b5ef9b3a15ddcbd8b867c82086a1ddd014383a5b..8bcf1bf0ad9c991d3d2b9a3c4b7fba3441cf8e92 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2020-02-13  Paul Eggert  <eggert@cs.ucla.edu>
+
+       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  <eggert@cs.ucla.edu>
 
        lchmod: pacify Coverity CID 1491216
index 87aa0d191ad34356cb469782d7e1855d530ab93e..02e2da956e634856e0bf5a6542ab4a2071ee9dfb 100644 (file)
@@ -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);
index 5fc658023ca6c76750b6b48a793bd951cdcf9ad7..c7191c07d89fc07dd88818621a7a7845b964d796 100644 (file)
@@ -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
 }