Regarding the version in which the bug was introduced, this code looks
like it was introduced with the rest of the SELinux code in 6.9.90,
which is marked as a beta release. However, it looks like it wasn't
actually used until 6.12, specifically commit eba365275 (install:
reenable matchpathcon-related code, 2008-05-31).

It would be nice to add a test for it, but it seems that
selabel_lookup_raw returns ENOENT for newly created directories in my
checkout, so the subsequent lsetfilecon_raw call will not be executed.
I was able to check it by installing files into my home directory,
though. Obviously, I do not want to do that in the test suite...

-- 8< --

* src/install.c: Include selinux-at.h.
(need_copy): Expect a directory file descriptor and relative name
argument, instead of a file name. Use them when getting the security
context of the destination.
(setdefaultfilecon): Expect a directory file descriptor and relative
name argument. Use it when setting the security context of the
destination.
(copy_file): Adjust call to need_copy.
(change_attributes): Adjust call to setdefaultfilecon.
* gnulib: Update for commit dd5b313c3f (selinux-at: Define functions
that don't perform context translation., 2026-11-10).
* NEWS: Mention the bug fix.

Discovered by Anthropic's OSS Scanner, as bug ANT-2026-XB6SGSHL.
---
 NEWS          |  5 +++++
 gnulib        |  2 +-
 src/install.c | 22 +++++++++++-----------
 3 files changed, 17 insertions(+), 12 deletions(-)

diff --git a/NEWS b/NEWS
index 3361336ef..382ac0c41 100644
--- a/NEWS
+++ b/NEWS
@@ -116,6 +116,11 @@ GNU coreutils NEWS                                    -*- 
outline -*-
   the current directory, or if --strip-program specified a relative path.
   [bug introduced in coreutils-6.2]
 
+  'install' now sets the default SELinux security context of an installed file,
+  or gets the current context when --compare is used, using an open file
+  descriptor to the destination directory.
+  [bug introduced in coreutils-6.12]
+
   'ls' no longer omits ACL indicators or capability coloring for subsequent
   files on the same file system after an unsupported security context
   lookup on a file without an ACL.
diff --git a/gnulib b/gnulib
index 8b4da9a18..02eb82743 160000
--- a/gnulib
+++ b/gnulib
@@ -1 +1 @@
-Subproject commit 8b4da9a18f1b31e499100246107581936245a0b7
+Subproject commit 02eb8274389a58565fb83ef7365f56a1a609456b
diff --git a/src/install.c b/src/install.c
index 1bb567786..7483017cd 100644
--- a/src/install.c
+++ b/src/install.c
@@ -40,6 +40,7 @@
 #include "quote.h"
 #include "savewd.h"
 #include "selinux.h"
+#include "selinux-at.h"
 #include "stat-time.h"
 #include "targetdir.h"
 #include "utimens.h"
@@ -169,8 +170,7 @@ extra_mode (mode_t input)
 /* Return true if copy of file SRC_NAME to file DEST_NAME aka
    DEST_DIRFD+DEST_RELNAME is necessary. */
 static bool
-need_copy (char const *src_name, char const *dest_name,
-           int dest_dirfd, char const *dest_relname,
+need_copy (char const *src_name, int const dest_dirfd, char const 
*dest_relname,
            const struct cp_options *x)
 {
   if (extra_mode (mode))
@@ -221,7 +221,7 @@ need_copy (char const *src_name, char const *dest_name,
         return true;
 
       char *to_scontext_raw = NULL;
-      if (getfilecon_raw (dest_name, &to_scontext_raw) == -1)
+      if (getfileconat_raw (dest_dirfd, dest_relname, &to_scontext_raw) == -1)
         {
           freecon (file_scontext_raw);
           return true;
@@ -320,7 +320,7 @@ get_labeling_handle (void)
    context.  Note this sets the context to that returned by selabel_lookup
    and thus discards MLS levels and user identity of the FILE.  */
 static void
-setdefaultfilecon (char const *file)
+setdefaultfilecon (char const *name, int const dirfd, char const *relname)
 {
   if (selinux_enabled != 1)
     {
@@ -329,7 +329,7 @@ setdefaultfilecon (char const *file)
     }
 
   struct stat st;
-  if (lstat (file, &st) != 0)
+  if (fstatat (dirfd, relname, &st, AT_SYMLINK_NOFOLLOW) < 0)
     return;
 
   struct selabel_handle *hnd = get_labeling_handle ();
@@ -337,18 +337,18 @@ setdefaultfilecon (char const *file)
     return;
 
   char *scontext_raw = NULL;
-  if (selabel_lookup_raw (hnd, &scontext_raw, file, st.st_mode) != 0)
+  if (selabel_lookup_raw (hnd, &scontext_raw, name, st.st_mode) != 0)
     {
       if (errno != ENOENT && ! ignorable_ctx_err (errno))
         error (0, errno, _("warning: %s: context lookup failed"),
-               quotef (file));
+               quotef (name));
       return;
     }
 
-  if (lsetfilecon_raw (file, scontext_raw) < 0 && errno != ENOTSUP)
+  if (lsetfileconat_raw (dirfd, relname, scontext_raw) < 0 && errno != ENOTSUP)
     error (0, errno,
            _("warning: %s: failed to change context to %s"),
-           quotef_n (0, file), quote_n (1, scontext_raw));
+           quotef_n (0, name), quote_n (1, scontext_raw));
 
   freecon (scontext_raw);
 }
@@ -419,7 +419,7 @@ static enum copy_status
 copy_file (char const *from, char const *to,
            int to_dirfd, char const *to_relname, const struct cp_options *x)
 {
-  if (copy_only_if_needed && !need_copy (from, to, to_dirfd, to_relname, x))
+  if (copy_only_if_needed && !need_copy (from, to_dirfd, to_relname, x))
     return COPY_SKIPPED;
 
   /* Allow installing from non-regular files like /dev/null.
@@ -461,7 +461,7 @@ change_attributes (char const *name, int dirfd, char const 
*relname)
     ok = true;
 
   if (use_default_selinux_context)
-    setdefaultfilecon (name);
+    setdefaultfilecon (name, dirfd, relname);
 
   return ok;
 }
-- 
2.56.0


Reply via email to