]> sourceware.org Git - glibc.git/commitdiff
linux: Use fchmodat2 on fchmod for flags different than 0 (BZ 26401)
authorAdhemerval Zanella <adhemerval.zanella@linaro.org>
Thu, 28 Sep 2023 16:56:21 +0000 (13:56 -0300)
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>
Mon, 20 Nov 2023 16:15:24 +0000 (13:15 -0300)
Linux 6.6 (09da082b07bbae1c) added support for fchmodat2, which has
similar semantics as fchmodat with an extra flag argument.  This
allows fchmodat to implement AT_SYMLINK_NOFOLLOW and AT_EMPTY_PATH
without the need for procfs.

The syscall is registered on all architectures (with value of 452
except on alpha which is 562, commit 78252deb023cf087).

The tst-lchmod.c requires a small fix where fchmodat checks two
contradictory assertions ('(st.st_mode & 0777) == 2' and
'(st.st_mode & 0777) == 3').

Checked on x86_64-linux-gnu on a 6.6 kernel.

Reviewed-by: Florian Weimer <fweimer@redhat.com>
io/tst-lchmod.c
sysdeps/unix/sysv/linux/fchmodat.c
sysdeps/unix/sysv/linux/kernel-features.h

index 2bf4835b05c1fdeb10d014a0f596929dd4d08faf..6496dc61e0a70fe5649385bed7f82af2f0a4420a 100644 (file)
@@ -219,9 +219,9 @@ test_1 (bool do_relative_path, int (*chmod_func) (int fd, const char *, mode_t,
          /* The error code from the openat fallback leaks out.  */
          if (errno != ENFILE && errno != EMFILE)
            TEST_COMPARE (errno, EOPNOTSUPP);
+        xstat (path_file, &st);
+        TEST_COMPARE (st.st_mode & 0777, 3);
        }
-     xstat (path_file, &st);
-     TEST_COMPARE (st.st_mode & 0777, 3);
 
      /* Close the descriptors.  */
      for (int *pfd = fd_list_begin (&fd_list); pfd < fd_list_end (&fd_list);
index 99527a3727e44cb8661ee1f743068f108ec93979..99d3df64404af2c8ab3be12bfd450816da355e35 100644 (file)
 #include <sysdep.h>
 #include <unistd.h>
 
-int
-fchmodat (int fd, const char *file, mode_t mode, int flag)
+#if !__ASSUME_FCHMODAT2
+static int
+fchmodat_fallback (int fd, const char *file, mode_t mode, int flag)
 {
-  if (flag == 0)
-    return INLINE_SYSCALL (fchmodat, 3, fd, file, mode);
-  else if (flag != AT_SYMLINK_NOFOLLOW)
+  if (flag != AT_SYMLINK_NOFOLLOW)
     return INLINE_SYSCALL_ERROR_RETURN_VALUE (EINVAL);
-  else
-    {
-      /* The kernel system call does not have a mode argument.
-        However, we can create an O_PATH descriptor and change that
-        via /proc (which does not resolve symbolic links).  */
 
-      int pathfd = __openat_nocancel (fd, file,
-                                     O_PATH | O_NOFOLLOW | O_CLOEXEC);
-      if (pathfd < 0)
-       /* This may report errors such as ENFILE and EMFILE.  The
-          caller can treat them as temporary if necessary.  */
-       return pathfd;
+  /* The kernel system call does not have a mode argument.
+     However, we can create an O_PATH descriptor and change that
+     via /proc (which does not resolve symbolic links).  */
 
-      /* Use fstatat because fstat does not work on O_PATH descriptors
-        before Linux 3.6.  */
-      struct __stat64_t64 st;
-      if (__fstatat64_time64 (pathfd, "", &st, AT_EMPTY_PATH) != 0)
-       {
-         __close_nocancel (pathfd);
-         return -1;
-       }
+  int pathfd = __openat_nocancel (fd, file,
+                                 O_PATH | O_NOFOLLOW | O_CLOEXEC);
+  if (pathfd < 0)
+    /* This may report errors such as ENFILE and EMFILE.  The
+       caller can treat them as temporary if necessary.  */
+    return pathfd;
 
-      /* Some Linux versions with some file systems can actually
-        change symbolic link permissions via /proc, but this is not
-        intentional, and it gives inconsistent results (e.g., error
-        return despite mode change).  The expected behavior is that
-        symbolic link modes cannot be changed at all, and this check
-        enforces that.  */
-      if (S_ISLNK (st.st_mode))
-       {
-         __close_nocancel (pathfd);
-         __set_errno (EOPNOTSUPP);
-         return -1;
-       }
+  /* Use fstatat because fstat does not work on O_PATH descriptors
+     before Linux 3.6.  */
+  struct __stat64_t64 st;
+  if (__fstatat64_time64 (pathfd, "", &st, AT_EMPTY_PATH) != 0)
+    {
+      __close_nocancel (pathfd);
+      return -1;
+    }
 
-      /* For most file systems, fchmod does not operate on O_PATH
-        descriptors, so go through /proc.  */
-      struct fd_to_filename filename;
-      int ret = __chmod (__fd_to_filename (pathfd, &filename), mode);
-      if (ret != 0)
-       {
-         if (errno == ENOENT)
-           /* /proc has not been mounted.  Without /proc, there is no
-              way to upgrade the O_PATH descriptor to a full
-              descriptor.  It is also not possible to re-open the
-              file without O_PATH because the file name may refer to
-              another file, and opening that without O_PATH may have
-              side effects (such as blocking, device rewinding, or
-              releasing POSIX locks).  */
-           __set_errno (EOPNOTSUPP);
-       }
+  /* Some Linux versions with some file systems can actually
+     change symbolic link permissions via /proc, but this is not
+     intentional, and it gives inconsistent results (e.g., error
+     return despite mode change).  The expected behavior is that
+     symbolic link modes cannot be changed at all, and this check
+     enforces that.  */
+  if (S_ISLNK (st.st_mode))
+    {
       __close_nocancel (pathfd);
-      return ret;
+      __set_errno (EOPNOTSUPP);
+      return -1;
+    }
+
+  /* For most file systems, fchmod does not operate on O_PATH
+     descriptors, so go through /proc.  */
+  struct fd_to_filename filename;
+  int ret = __chmod (__fd_to_filename (pathfd, &filename), mode);
+  if (ret != 0)
+    {
+      if (errno == ENOENT)
+       /* /proc has not been mounted.  Without /proc, there is no
+          way to upgrade the O_PATH descriptor to a full
+          descriptor.  It is also not possible to re-open the
+          file without O_PATH because the file name may refer to
+          another file, and opening that without O_PATH may have
+          side effects (such as blocking, device rewinding, or
+          releasing POSIX locks).  */
+       __set_errno (EOPNOTSUPP);
     }
+  __close_nocancel (pathfd);
+  return ret;
+}
+#endif
+
+int
+fchmodat (int fd, const char *file, mode_t mode, int flag)
+{
+#if __ASSUME_FCHMODAT2
+  return INLINE_SYSCALL_CALL (fchmodat2, fd, file, mode, flag);
+#else
+  if (flag == 0)
+    return INLINE_SYSCALL_CALL (fchmodat, fd, file, mode);
+
+  int r = INLINE_SYSCALL_CALL (fchmodat2, fd, file, mode, flag);
+  if (r != 0 && errno == ENOSYS)
+    return fchmodat_fallback (fd, file, mode, flag);
+  return r;
+#endif
 }
 libc_hidden_def (fchmodat)
index 07b440f4eea364b05fa49bf71ceebf78f80efe13..670d2604d2c47f784675a14a585ca6ed0a55a961 100644 (file)
 # define __ASSUME_CLONE3 0
 #endif
 
+/* The fchmodat2 system call was introduced across all architectures
+   in Linux 6.6.  */
+#if __LINUX_KERNEL_VERSION >= 0x060600
+# define __ASSUME_FCHMODAT2 1
+#else
+# define __ASSUME_FCHMODAT2 0
+#endif
+
 #endif /* kernel-features.h */
This page took 2.191645 seconds and 5 git commands to generate.