* 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