* src/mkfifo.c: Include fcntl--.h.
(main): When a mode is specified, open a file descriptor to its parent
and use it to create the FIFO and set its mode.
* src/mknod.c: Include fcntl--.h.
(main): When a mode is specified, open a file descriptor to its parent
and use it to create the special file and set its mode.
* NEWS: Mention the bug fix.

Discovered by Anthropic's OSS Scanner, as bug ANT-2026-ACVILIMZ.
---
 NEWS         |  6 +++++
 src/mkfifo.c | 47 ++++++++++++++++++++++++++++++++----
 src/mknod.c  | 68 ++++++++++++++++++++++++++++++++++++++++------------
 3 files changed, 101 insertions(+), 20 deletions(-)

diff --git a/NEWS b/NEWS
index 0d7548fad..d3aed2dbb 100644
--- a/NEWS
+++ b/NEWS
@@ -90,6 +90,12 @@ GNU coreutils NEWS                                    -*- 
outline -*-
   lookup on a file without an ACL.
   [bug introduced in coreutils-9.6]
 
+  'mkfifo' and 'mknod' no longer have a race when setting the mode specified by
+  --mode.  Previously, they used lchmod, which follows symbolic links in the
+  leading path components, to ensure the mode was properly set in the presence
+  of default ACLs.
+  [bug introduced in coreutils-8.22]
+
   'mv -Z' no longer blocks when setting the SELinux security context of a
   just-moved FIFO.
   [bug introduced with SELinux support in coreutils-8.22]
diff --git a/src/mkfifo.c b/src/mkfifo.c
index 12d9a423e..14368b6e1 100644
--- a/src/mkfifo.c
+++ b/src/mkfifo.c
@@ -23,6 +23,7 @@
 #include <selinux/label.h>
 
 #include "system.h"
+#include "fcntl--.h"
 #include "modechange.h"
 #include "quote.h"
 #include "selinux.h"
@@ -169,19 +170,55 @@ main (int argc, char **argv)
   int exit_status = EXIT_SUCCESS;
   for (; optind < argc; ++optind)
     {
+      char const *const name = argv[optind];
+
       if (set_security_context)
-        defaultcon (set_security_context, argv[optind], S_IFIFO);
-      if (mkfifo (argv[optind], newmode) != 0)
+        defaultcon (set_security_context, name, S_IFIFO);
+
+      int dirfd = AT_FDCWD;
+      char const *relname = name;
+      char const *base;
+
+      /* When a mode is specified, open a file descriptor to the parent
+         and create the FIFO relative to that.  This allows us to call
+         lchmodat instead of lchmod, which would follow leading path
+         components in NAME.  The lchmodat call is necessary to set
+         the mode in the presence of default ACLs, where we may have
+         mode different than the one the user specified without it.  */
+      if (specified_mode && (base = last_component (name)) != name && *base)
+        {
+          char *dir = dir_name (name);
+
+          dirfd = open (dir, O_PATHSEARCH | O_DIRECTORY);
+          free (dir);
+
+          /* We lie about the error here, since the reason for opening the
+             parent is not intuitive, and a mkfifo(3) call would most likely
+             fail for the same reason.  */
+          if (dirfd < 0)
+            {
+              error (0, errno, _("cannot create fifo %s"), quoteaf (name));
+              exit_status = EXIT_FAILURE;
+              continue;
+            }
+
+          relname = base;
+        }
+
+      if (mkfifoat (dirfd, relname, newmode) < 0)
         {
-          error (0, errno, _("cannot create fifo %s"), quoteaf (argv[optind]));
+          error (0, errno, _("cannot create fifo %s"), quoteaf (name));
           exit_status = EXIT_FAILURE;
         }
-      else if (specified_mode && lchmod (argv[optind], newmode) != 0)
+      else if (specified_mode && lchmodat (dirfd, relname, newmode) < 0)
         {
           error (0, errno, _("cannot set permissions of %s"),
-                 quoteaf (argv[optind]));
+                 quoteaf (name));
           exit_status = EXIT_FAILURE;
         }
+
+      if (dirfd != AT_FDCWD)
+        close (dirfd);
     }
 
   return exit_status;
diff --git a/src/mknod.c b/src/mknod.c
index 59b844fcd..57c2f8ff5 100644
--- a/src/mknod.c
+++ b/src/mknod.c
@@ -23,6 +23,7 @@
 #include <selinux/label.h>
 
 #include "system.h"
+#include "fcntl--.h"
 #include "modechange.h"
 #include "quote.h"
 #include "selinux.h"
@@ -209,6 +210,7 @@ main (int argc, char **argv)
      'mknod /dev/rst0 character 18 0'. */
 
   mode_t node_type;
+  dev_t device;
   switch (argv[optind + 1][0])
     {
     case 'b':                  /* 'block' or 'buffered' */
@@ -244,26 +246,17 @@ main (int argc, char **argv)
           error (EXIT_FAILURE, 0,
                  _("invalid minor device number %s"), quote (s_minor));
 
-        dev_t device = makedev (i_major, i_minor);
+        device = makedev (i_major, i_minor);
 #ifdef NODEV
         if (device == NODEV)
           error (EXIT_FAILURE, 0, _("invalid device %s %s"),
                  s_major, s_minor);
 #endif
-
-        if (set_security_context)
-          defaultcon (set_security_context, argv[optind], node_type);
-
-        if (mknod (argv[optind], newmode | node_type, device) != 0)
-          error (EXIT_FAILURE, errno, "%s", quotef (argv[optind]));
       }
       break;
 
     case 'p':                  /* 'pipe' */
-      if (set_security_context)
-        defaultcon (set_security_context, argv[optind], S_IFIFO);
-      if (mkfifo (argv[optind], newmode) != 0)
-        error (EXIT_FAILURE, errno, "%s", quotef (argv[optind]));
+      node_type = S_IFIFO;
       break;
 
     default:
@@ -271,9 +264,54 @@ main (int argc, char **argv)
       usage (EXIT_FAILURE);
     }
 
-  if (specified_mode && lchmod (argv[optind], newmode) != 0)
-    error (EXIT_FAILURE, errno, _("cannot set permissions of %s"),
-           quoteaf (argv[optind]));
+  char const *const name = argv[optind];
+
+  if (set_security_context)
+    defaultcon (set_security_context, name, node_type);
+
+  int dirfd = AT_FDCWD;
+  char const *relname = name;
+  char const *base;
+
+  /* When a mode is specified, open a file descriptor to the parent
+     and create the special file relative to that.  This allows us to
+     call lchmodat instead of lchmod, which would follow leading path
+     components in NAME.  The lchmodat call is necessary to set
+     the mode in the presence of default ACLs, where we may have
+     mode different than the one the user specified without it.  */
+  if (specified_mode && (base = last_component (name)) != name && *base)
+    {
+      char *dir = dir_name (name);
+
+      dirfd = open (dir, O_PATHSEARCH | O_DIRECTORY);
+      free (dir);
+
+      /* We lie about the error here, since the reason for opening the
+         parent is not intuitive, and a mkfifo(3) or mknodat(2) call
+         would most likely fail for the same reason.  */
+      if (dirfd < 0)
+        error (EXIT_FAILURE, errno, "%s", quotef (name));
+
+      relname = base;
+    }
+
+  bool ok = true;
+  if ((node_type == S_IFIFO
+       ? mkfifoat (dirfd, relname, newmode)
+       : mknodat (dirfd, relname, newmode | node_type, device)) < 0)
+    {
+      error (0, errno, "%s", quotef (name));
+      ok = false;
+    }
+  else if (specified_mode && lchmodat (dirfd, relname, newmode) < 0)
+    {
+      error (EXIT_FAILURE, errno, _("cannot set permissions of %s"),
+             quoteaf (name));
+      ok = false;
+    }
+
+  if (dirfd != AT_FDCWD)
+    close (dirfd);
 
-  return EXIT_SUCCESS;
+  return ok ? EXIT_SUCCESS : EXIT_FAILURE;
 }
-- 
2.56.0


Reply via email to