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