* src/dircolors.c (syntax): New global variable.
(append_quoted): Escape '!' if we are using 'csh' syntax to prevent
history substitution.
(main): Remove the syntax variable that was made global.
* tests/misc/dircolors.pl (@Tests): Add a test case.
* NEWS: Mention the bug fix.

Discovered by Anthropic's OSS Scanner, as bug ANT-2026-SFBDLZ6V.
---
 NEWS                    |  6 ++++++
 src/dircolors.c         | 11 ++++++++++-
 tests/misc/dircolors.pl |  5 +++++
 3 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index b1d8f09ec..a856efff8 100644
--- a/NEWS
+++ b/NEWS
@@ -71,6 +71,12 @@ GNU coreutils NEWS                                    -*- 
outline -*-
   oflag=seek_bytes, and when N is smaller than the output block size.
   [bug introduced with oflag=seek_bytes in coreutils-8.16]
 
+  'dircolors --csh' now escapes '!' characters in its output.  Previously, csh
+  would perform history substitution on the output when evaluated, which would
+  fail or execute arbitrary commands depending on the contents of the database
+  file.
+  [This bug was present in "the beginning".]
+
   'factor' no longer treats inputs requiring the use of arbitrary-precision
   integers followed by a non-numeric suffix as zero if the suffix includes
   non-space characters, or the number itself when followed by spaces.
diff --git a/src/dircolors.c b/src/dircolors.c
index 72f62820c..b7b1c72da 100644
--- a/src/dircolors.c
+++ b/src/dircolors.c
@@ -44,6 +44,9 @@ enum Shell_syntax
   SHELL_SYNTAX_UNKNOWN
 };
 
+/* The shell syntax being used.  */
+static enum Shell_syntax syntax = SHELL_SYNTAX_UNKNOWN;
+
 #define APPEND_CHAR(C) obstack_1grow (&lsc_obstack, C)
 
 /* Accumulate in this obstack the value for the LS_COLORS environment
@@ -235,6 +238,13 @@ append_quoted (char const *str)
             need_backslash = !need_backslash;
             break;
 
+          case '!':
+            /* Prevent 'csh' from doing history substitution.  */
+            if (syntax == SHELL_SYNTAX_C)
+              APPEND_CHAR ('\\');
+            need_backslash = true;
+            break;
+
           case ':':
           case '=':
             if (need_backslash)
@@ -436,7 +446,6 @@ main (int argc, char **argv)
 {
   bool ok = true;
   int optc;
-  enum Shell_syntax syntax = SHELL_SYNTAX_UNKNOWN;
   bool print_database = false;
 
   initialize_main (&argc, &argv);
diff --git a/tests/misc/dircolors.pl b/tests/misc/dircolors.pl
index 404fbb075..6c0575c43 100755
--- a/tests/misc/dircolors.pl
+++ b/tests/misc/dircolors.pl
@@ -66,6 +66,11 @@ my @Tests =
      ['print-ls-colors', '--print-ls-colors', {IN => "OWT 40;33\n"},
       {OUT => "\x1B[40;33mtw\t40;33\x1B[0m\n"}],
 
+     ['csh-history-sub', '-c',
+      {IN => "*.a!#^:s\\LS_COLORS\\' ; touch ./a; echo '\n"},
+      {OUT => ("setenv LS_COLORS '*.a\\!#^:s\\LS_COLORS\\'\\''=; "
+               . "touch ./a; echo '\\'':'\n")}],
+
      # CAREFUL: always specify the -b option, unless explicitly testing
      # for csh syntax output, or --print-ls-color output.
     );
-- 
2.56.0


Reply via email to