[PATCH 6/8] fchmodat: don’t mishandle AT _EMPTY_PATH

Paul Eggert <[email protected]>
Newsgroups gmane.comp.lib.gnulib.bugs
Message-ID <[email protected]>
This does not add support for AT_EMPTY_PATH on platforms lacking it.
It merely fixes bugs in handling AT_EMPTY_PATH on platforms
that already have AT_EMPTY_PATH.  This is complicated by
the fact that glibc may define AT_EMPTY_PATH even though the
underlying kernel lacks support for it, and by the fact that
in recent kernels, AT_EMPTY_PATH allows the file name to be
a null pointer.
* lib/fchmodat.c (AT_EMPTY_PATH): Default to 0.
(rpl_fchmodat): Do not mishandle AT_EMPTY_PATH if specified
and if the underlying library+kernel supports it.
---
 ChangeLog      | 12 ++++++++++++
 lib/fchmodat.c | 48 ++++++++++++++++++++++++++++++++++--------------
 2 files changed, 46 insertions(+), 14 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 20459c0447..d78d0d1102 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
 2026-06-16  Paul Eggert  <[email protected]>
 
+	fchmodat: don’t mishandle AT_EMPTY_PATH
+	This does not add support for AT_EMPTY_PATH on platforms lacking it.
+	It merely fixes bugs in handling AT_EMPTY_PATH on platforms
+	that already have AT_EMPTY_PATH.  This is complicated by
+	the fact that glibc may define AT_EMPTY_PATH even though the
+	underlying kernel lacks support for it, and by the fact that
+	in recent kernels, AT_EMPTY_PATH allows the file name to be
+	a null pointer.
+	* lib/fchmodat.c (AT_EMPTY_PATH): Default to 0.
+	(rpl_fchmodat): Do not mishandle AT_EMPTY_PATH if specified
+	and if the underlying library+kernel supports it.
+
 	fstatat etc.: fix wrong machine code for calls
 	Without this fix, compilers can generate incorrect machine code
 	for functions that call to fstatat-like functions, as the
diff --git a/lib/fchmodat.c b/lib/fchmodat.c
index 9ac951ce68..c41768935b 100644
--- a/lib/fchmodat.c
+++ b/lib/fchmodat.c
@@ -48,18 +48,32 @@ orig_fchmodat (int dir, char const *file, mode_t mode, int flags)
 
 #include "issymlinkat.h"
 
+#ifndef AT_EMPTY_PATH
+# define AT_EMPTY_PATH 0
+#endif
+
 /* Invoke chmod or lchmod on FILE, using mode MODE, in the directory
    open on descriptor FD.  If possible, do it without changing the
    working directory.  Otherwise, resort to using save_cwd/fchdir,
    then (chmod|lchmod)/restore_cwd.  If either the save_cwd or the
    restore_cwd fails, then give a diagnostic and exit nonzero.
-   Note that an attempt to use a FLAG value of AT_SYMLINK_NOFOLLOW
-   on a system without lchmod support causes this function to fail.  */
+   Fail if FLAGS contains AT_SYMLINK_NOFOLLOW on a system lacking
+   lchmod support.  */
 
 #if HAVE_FCHMODAT
 int
 fchmodat (int dir, char const *file, mode_t mode, int flags)
 {
+  if (file && *file)
+    flags &= ~AT_EMPTY_PATH;
+  else if (! (flags & AT_EMPTY_PATH))
+    {
+      errno = ENOENT;
+      return -1;
+    }
+  else if (! (flags & AT_SYMLINK_NOFOLLOW))
+    return fchmod (dir, mode);
+
 # if HAVE_NEARLY_WORKING_FCHMODAT
   /* Correct the trailing slash handling.  */
   size_t len = strlen (file);
@@ -77,17 +91,21 @@ fchmodat (int dir, char const *file, mode_t mode, int flags)
 # endif
 
 # if NEED_FCHMODAT_NONSYMLINK_FIX
-  if (flags == AT_SYMLINK_NOFOLLOW)
+  if ((flags & ~AT_EMPTY_PATH) == AT_SYMLINK_NOFOLLOW)
     {
 #  if HAVE_READLINKAT
 #   ifdef O_PATH
-      /* Open a file descriptor with O_NOFOLLOW, to make sure we don't
-         follow symbolic links, if /proc is mounted.  O_PATH is used to
-         avoid a failure if the file is not readable.
-         Cf. <https://sourceware.org/PR14578>  */
-      int fd = openat (dir, file, O_PATH | O_NOFOLLOW | O_CLOEXEC);
-      if (fd < 0)
-        return fd;
+      int fd;
+      if (flags & AT_EMPTY_PATH)
+        fd = dir;
+      else
+        {
+          /* Open with O_NOFOLLOW, so we don't follow symlinks.
+             See <https://sourceware.org/PR14578>.  */
+          fd = openat (dir, file, O_PATH | O_NOFOLLOW | O_CLOEXEC);
+          if (fd < 0)
+            return fd;
+        }
 
       int err;
       {
@@ -105,7 +123,8 @@ fchmodat (int dir, char const *file, mode_t mode, int flags)
           err = errno == ENOENT ? -1 : errno;
       }
 
-      close (fd);
+      if (! (flags & AT_EMPTY_PATH))
+        close (fd);
 
       errno = err;
       if (0 <= err)
@@ -114,15 +133,16 @@ fchmodat (int dir, char const *file, mode_t mode, int flags)
 
       /* O_PATH + /proc is not supported.  */
 
-      if (issymlinkat (dir, file) > 0)
+      if (issymlinkat (dir, file ? file : "") > 0)
         {
           errno = EOPNOTSUPP;
           return -1;
         }
 #  endif
 
-      /* Fall back on orig_fchmodat with no flags, despite a possible race.  */
-      flags = 0;
+      /* Fall back on orig_fchmodat without AT_SYMLINK_NOFOLLOW,
+         despite a possible race.  */
+      flags &= ~AT_SYMLINK_NOFOLLOW;
     }
 # endif
 
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.