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


Reply via email to