On systems where the security context reports as unsupported,
and we encountered a file without ACLs, no further lookups
would be performed. Introduced in commit v9.5-156-gb857d66b5.
* src/ls.c (file_has_aclinfo_cache): Don't mark file system as
unsupported if there might be ACLs or capabilities present.
* tests/ls/acl-cache.sh: Add a new test.
* tests/local.mk: Reference the new test.
* tests/ls/acl.sh: Add a check.
* NEWS: Mention the bug fix.
---
NEWS | 5 ++++
src/ls.c | 3 +++
tests/local.mk | 1 +
tests/ls/acl-cache.sh | 55 +++++++++++++++++++++++++++++++++++++++++++
tests/ls/acl.sh | 5 ++++
5 files changed, 69 insertions(+)
create mode 100755 tests/ls/acl-cache.sh
diff --git a/NEWS b/NEWS
index 62a0f4176..70d0fc7f8 100644
--- a/NEWS
+++ b/NEWS
@@ -18,6 +18,11 @@ GNU coreutils NEWS -*-
outline -*-
after a tab character are accounted for correctly.
[bugs introduced in coreutils-9.8]
+ '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.
+ [bug introduced in coreutils-9.6]
+
'realpath' without -s, or 'readlink' with -e,-f,-m no longer
loop infinitely when resolving a symbolic link which
resolves to a path beginning with the symbolic link itself.
diff --git a/src/ls.c b/src/ls.c
index 288b657a2..8e6706647 100644
--- a/src/ls.c
+++ b/src/ls.c
@@ -3255,7 +3255,10 @@ file_has_aclinfo_cache (char const *file, struct
fileinfo *f,
errno = 0;
int n = file_has_aclinfo (file, ai, flags);
int err = errno;
+ /* A security-context error does not imply that ACLs are unsupported.
+ Require an unsupported attribute listing too. */
if (f->stat_ok && n <= 0 && !acl_errno_valid (err)
+ && ai->size < 0 && !acl_errno_valid (ai->u.err)
&& (!(flags & ACL_GET_SCONTEXT) || !acl_errno_valid (ai->scontext_err)))
{
unsupported_return = n;
diff --git a/tests/local.mk b/tests/local.mk
index 06d8f934d..ce88b44f6 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -716,6 +716,7 @@ all_tests = \
tests/ln/target-1.sh \
tests/ls/a-option.sh \
tests/ls/acl.sh \
+ tests/ls/acl-cache.sh \
tests/ls/abmon-align.sh \
tests/ls/birthtime.sh \
tests/ls/block-size.sh \
diff --git a/tests/ls/acl-cache.sh b/tests/ls/acl-cache.sh
new file mode 100755
index 000000000..fb7007f86
--- /dev/null
+++ b/tests/ls/acl-cache.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+# A failed security-context lookup must not disable ACL detection.
+# This failed from v9.6 to v9.12 inclusive.
+
+# Copyright (C) 2026 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ ls
+require_strace_ getxattr,listxattr
+require_setfacl_
+
+grep '^#define USE_ACL 1' $CONFIG_HEADER > /dev/null ||
+ skip_ 'insufficient ACL support'
+
+touch acl plain || framework_failure_
+uid=$(id -u) || framework_failure_
+setfacl -m "u:$uid:rw" acl && setfacl -b plain || framework_failure_
+
+ls -l acl > exp || framework_failure_
+grep '^..........+' exp > /dev/null || skip_ 'ls does not report ACLs'
+
+# Require injection to affect only the selected path.
+strace --quiet=all -o /dev/null -P plain \
+ -e fault=listxattr,llistxattr:error=EIO ls -l acl > out 2> err &&
+compare exp out || skip_ 'strace does not support path-filtered injection'
+
+# Ensure a failed getxattr doesn't induce failure to lookup ACLs.
+# Also check the fallback when listxattr fails with E2BIG.
+for list_fault in '' '-e fault=listxattr,llistxattr:error=E2BIG'; do
+ strace --quiet=all -o trace -P plain \
+ -e trace=listxattr,llistxattr,getxattr,lgetxattr \
+ -e fault=getxattr,lgetxattr:error=EOPNOTSUPP \
+ $list_fault ls -l acl plain acl > out 2> err || fail=1
+ test "$DEBUG" && cat trace
+ compare /dev/null err || fail=1
+ test "$(grep -c '^..........+' out)" = 2 || fail=1
+done
+
+grep ' (INJECTED)' trace > /dev/null ||
+ skip_ 'strace did not inject the requested failure'
+
+Exit $fail
diff --git a/tests/ls/acl.sh b/tests/ls/acl.sh
index 74e63226b..729b1b632 100755
--- a/tests/ls/acl.sh
+++ b/tests/ls/acl.sh
@@ -52,4 +52,9 @@ gap1=$(ls -l pad/f1 | sed -n 's/^[^+]*+\( *\)[0-9].*/\1/p')
gap5=$(ls -l pad | sed -n 's/^[^+]*+\( *\)[0-9].*/\1/p' | head -n1)
test "x$gap1" = "x$gap5" || fail=1
+# A file without an ACL must not suppress ACLs on subsequent files.
+touch pad/plain && setfacl -b pad/plain || framework_failure_
+ls -l pad/f1 pad/plain pad/f1 > out || fail=1
+test "$(grep -c '^..........+' out)" = 2 || fail=1
+
Exit $fail
--
2.55.0