Similarly to commit v9.10-115-gb3fe24213, which applied
the same quoting mechanism to --check operation,
also quote file names when generating checksums to tty.
This change gives these advantages:

  1. Avoids dumping arbitrary data to vulnerable terminals
  2. Avoids visual deception with ansi codes with unknown file names
  3. More secure if users copy and paste file names from output

* doc/coreutils.texi (sum invocation): Mention the tty quoting
and reference the details from ...
(cksum output modes): Describe the two forms of quoting used.
* tests/misc/tty-quoting.sh: Add test cases.
* NEWS: Mention the improvement.

Discovered by Anthropic's OSS Scanner, as bug ANT-2026-Z25BP6MW.
---
 NEWS                      |   4 ++
 doc/coreutils.texi        | 148 +++++++++++++++++++-------------------
 src/cksum.c               |  26 +++++--
 tests/misc/tty-quoting.sh |  16 ++++-
 4 files changed, 117 insertions(+), 77 deletions(-)

diff --git a/NEWS b/NEWS
index 73f0ccb2e..4be22db47 100644
--- a/NEWS
+++ b/NEWS
@@ -288,6 +288,10 @@ GNU coreutils NEWS                                    -*- 
outline -*-
 
   'basenc --base58' encoding speed is 20x faster when built without libgmp.
 
+  'cksum' when generating checksums to tty, uses shell quoting when required,
+  to more robustly escape presented file names, honoring $QUOTING_STYLE to
+  adjust or disable quoting. This also affects md5sum, sha*sum, b2sum, and sum.
+
   'cut -c' is up to 10x faster in UTF-8 locales, and
   'cut -f -w' is up to 10x faster in uni-byte locales.
 
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 15eaef800..2905c1d75 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -3882,6 +3882,8 @@ sum [@var{option}]@dots{} [@var{file}]@dots{}
 @command{sum} prints the checksum for each @var{file} followed by the
 number of blocks in the file (rounded up).  If at least one @var{file}
 is given, file names are also printed.
+When standard output is a terminal, file names are quoted
+as described for @command{cksum}.  @xref{cksum output modes}.
 
 By default, GNU @command{sum} computes checksums using an algorithm
 compatible with BSD @command{sum} and prints file sizes in units of
@@ -3984,13 +3986,85 @@ otherwise text mode is the default.
 @end table
 
 Without @option{--zero}, and with non legacy output formats,
-if @var{file} contains a backslash, newline, or carriage return,
+and when not outputting to a terminal or if terminal quoting has been disabled,
+the following quoting mode is in effect.
+If @var{file} contains a backslash, newline, or carriage return,
 the line is started with a backslash, and each problematic character
 in the file name is escaped with a backslash, making the output unambiguous
 even in the presence of arbitrary file names.
 Since the backslash character itself is escaped, any other backslash
 escape sequences are reserved for future use.
 
+@macro quotingStyles
+@table @samp
+@item literal
+Output strings as-is;
+@ifset LS_COMMAND
+this is the same as the @option{--literal} (@option{-N}) option.
+@end ifset
+@item shell
+Quote strings for the shell if they contain shell metacharacters or would
+cause ambiguous output.
+The quoting is suitable for POSIX-compatible shells like
+@command{bash}, but it does not always work for incompatible shells
+like @command{csh}.
+@item shell-always
+Quote strings for the shell, even if they would normally not require quoting.
+@item shell-escape
+Like @samp{shell}, but also quote non-printable characters using the POSIX
+@samp{$''} syntax suitable for most shells. This is the most general format
+as any file name is represented unambiguously and safely.  I.e., the full
+quoted string can be pasted back to the shell to refer to any file.
+@ifset LS_COMMAND
+Consequently, this is the default format used when @command{ls}
+is outputting to a tty.
+@end ifset
+Note also that specifying the C locale with @code{LC_ALL=C} can be useful
+with this output format, to avoid character set conversion issues
+with some terminals; e.g., xterm always converting to unicode composed form.
+See also
+@uref{https://www.gnu.org/software/coreutils/quotes.html, Quoting file names}.
+@item shell-escape-always
+Like @samp{shell-escape}, but quote strings even if they would
+normally not require quoting.
+@item c
+Quote strings as for C character string literals, including the
+surrounding double-quote characters;
+@ifset LS_COMMAND
+this is the same as the @option{--quote-name} (@option{-Q}) option.
+@end ifset
+@item c-maybe
+Quote strings as for C character string literals, except omit the
+surrounding double-quote if no escaping is required.
+@item escape
+Quote strings as for C character string literals, except omit the
+surrounding double-quote characters;
+@ifset LS_COMMAND
+this is the same as the @option{--escape} (@option{-b}) option.
+@end ifset
+@item clocale
+Quote strings as for C character string literals, except use
+surrounding quotation marks appropriate for the
+locale.
+@item locale
+@c Use @t instead of @samp to avoid duplicate quoting in some output styles.
+Quote strings as for C character string literals, except use
+surrounding quotation marks appropriate for the locale, and quote
+@t{'like this'} instead of @t{"like
+this"} in the default C locale.  This looks nicer on many displays.
+@end table
+@end macro
+
+@macro terminalQuoted
+Without @option{--zero} and when standard output is a terminal,
+file names are quoted using the @samp{shell-escape} style.
+The environment variable @env{QUOTING_STYLE} specifies the quoting style.
+Valid quoting styles are:
+@quotingStyles
+@end macro
+
+@terminalQuoted
+
 @node cksum general options
 @subsection cksum general options
 
@@ -8503,65 +8577,6 @@ Use style @var{word} to quote file names and other 
strings that may
 contain arbitrary characters.  The @var{word} should
 be one of the following:
 
-@macro quotingStyles
-@table @samp
-@item literal
-Output strings as-is;
-@ifset LS_COMMAND
-this is the same as the @option{--literal} (@option{-N}) option.
-@end ifset
-@item shell
-Quote strings for the shell if they contain shell metacharacters or would
-cause ambiguous output.
-The quoting is suitable for POSIX-compatible shells like
-@command{bash}, but it does not always work for incompatible shells
-like @command{csh}.
-@item shell-always
-Quote strings for the shell, even if they would normally not require quoting.
-@item shell-escape
-Like @samp{shell}, but also quote non-printable characters using the POSIX
-@samp{$''} syntax suitable for most shells. This is the most general format
-as any file name is represented unambiguously and safely.  I.e., the full
-quoted string can be pasted back to the shell to refer to any file.
-@ifset LS_COMMAND
-Consequently, this is the default format used when @command{ls}
-is outputting to a tty.
-@end ifset
-Note also that specifying the C locale with @code{LC_ALL=C} can be useful
-with this output format, to avoid character set conversion issues
-with some terminals; e.g., xterm always converting to unicode composed form.
-See also
-@uref{https://www.gnu.org/software/coreutils/quotes.html, Quoting file names}.
-@item shell-escape-always
-Like @samp{shell-escape}, but quote strings even if they would
-normally not require quoting.
-@item c
-Quote strings as for C character string literals, including the
-surrounding double-quote characters;
-@ifset LS_COMMAND
-this is the same as the @option{--quote-name} (@option{-Q}) option.
-@end ifset
-@item c-maybe
-Quote strings as for C character string literals, except omit the
-surrounding double-quote if no escaping is required.
-@item escape
-Quote strings as for C character string literals, except omit the
-surrounding double-quote characters;
-@ifset LS_COMMAND
-this is the same as the @option{--escape} (@option{-b}) option.
-@end ifset
-@item clocale
-Quote strings as for C character string literals, except use
-surrounding quotation marks appropriate for the
-locale.
-@item locale
-@c Use @t instead of @samp to avoid duplicate quoting in some output styles.
-Quote strings as for C character string literals, except use
-surrounding quotation marks appropriate for the locale, and quote
-@t{'like this'} instead of @t{"like
-this"} in the default C locale.  This looks nicer on many displays.
-@end table
-@end macro
 @set LS_COMMAND
 @quotingStyles
 @clear LS_COMMAND
@@ -11114,14 +11129,6 @@ variable is set.
 
 @end table
 
-@c This is also used by basename and dirname.
-@macro terminalQuoted
-When standard output is a terminal, output is quoted using the
-@samp{shell-escape} style.  The environment variable
-@env{QUOTING_STYLE} specifies the quoting style.  Valid quoting styles
-are:
-@quotingStyles
-@end macro
 @terminalQuoted
 
 The @command{readlink} utility first appeared in OpenBSD 2.1.
@@ -14629,10 +14636,7 @@ only on the file name, and does not touch any actual 
file.
 
 @end table
 
-When standard output is a terminal, file names are quoted using
-the @samp{shell-escape} style.  The environment variable
-@env{QUOTING_STYLE} can select the quoting style.  Valid quoting styles are:
-@quotingStyles
+@terminalQuoted
 
 @cindex exit status of @command{realpath}
 Exit status:
diff --git a/src/cksum.c b/src/cksum.c
index bdd54c74a..1fdfcf8fd 100644
--- a/src/cksum.c
+++ b/src/cksum.c
@@ -21,9 +21,9 @@
 #include <getopt.h>
 #include <sys/types.h>
 
+#include "argmatch.h"
 #include "assure.h"
 #include "system.h"
-#include "argmatch.h"
 #include "c-ctype.h"
 #include "quote.h"
 #include "xdectoint.h"
@@ -231,6 +231,9 @@ static bool base64_digest = false;
 /* If true, print binary digests, not hex.  */
 static bool raw_digest = false;
 
+/* If true, quote output names according to the selected quoting style.  */
+static bool quote_output;
+
 /* blake2b and sha3 allow the -l option.  Luckily they both have the same
    maximum digest size.  */
 #if HASH_ALGO_BLAKE2 || HASH_ALGO_CKSUM
@@ -1271,8 +1274,10 @@ output_file (char const *file, int binary_file, void 
const *digest,
 
   unsigned char const *bin_buffer = digest;
 
-  /* Output a leading backslash if the file name contains problematic chars.  
*/
-  bool needs_escape = delim == '\n' && problematic_chars (file);
+  /* Output a leading backslash if the file name contains problematic chars,
+     unless we're already quoting output.  */
+  bool needs_escape = (! quote_output && delim == '\n'
+                       && problematic_chars (file));
 
   if (needs_escape)
     putchar ('\\');
@@ -1870,6 +1875,18 @@ main (int argc, char **argv)
   else if (prefix_tag)
     binary = 1;
 
+  if (!do_check && digest_delim == '\n' && isatty (STDOUT_FILENO))
+    {
+      int qs = getenv_quoting_style ();
+      if (qs < 0)
+        qs = shell_escape_quoting_style;
+      if (qs != literal_quoting_style)
+        {
+          set_quoting_style (NULL, qs);
+          quote_output = true;
+        }
+    }
+
   char **operand_lim = argv + argc;
   if (optind == argc)
     *operand_lim++ = bad_cast ("-");
@@ -1892,7 +1909,8 @@ main (int argc, char **argv)
             ok = false;
           else
             {
-              DIGEST_OUT (file, binary_file, bin_buffer, raw_digest, 
prefix_tag,
+              DIGEST_OUT (quote_output ? quoteN (file) : file, binary_file,
+                          bin_buffer, raw_digest, prefix_tag,
                           digest_delim, optind != argc, length);
               if (ferror (stdout))
                 write_error ();
diff --git a/tests/misc/tty-quoting.sh b/tests/misc/tty-quoting.sh
index b967c45d1..b4eee247c 100755
--- a/tests/misc/tty-quoting.sh
+++ b/tests/misc/tty-quoting.sh
@@ -17,7 +17,7 @@
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 . "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
-print_ver_ basename dirname du ls readlink realpath printf test
+print_ver_ basename cksum dirname du ls readlink realpath printf test
 require_strace_ ioctl
 
 touch 'b ar' || framework_failure_
@@ -67,4 +67,18 @@ for cmd in basename du dirname 'ls -w0' readlink 'realpath 
--relative-to=.' \
   compare quoted printf_quoted || fail=1
 done
 
+for cmd in 'cksum -a sha1 --untagged' 'cksum -a sha1'; do
+  file='f oo'
+
+  run_tty_ $cmd "$file" >full_quoted || fail=1
+
+  # Ensure shell quoting found
+  cut -d "'" -f2 full_quoted >unquoted || framework_failure_
+  test "$file" = "$(cat unquoted)" || fail=1
+
+  # Ensure unquoted doesn't match quoted
+  QUOTING_STYLE=literal run_tty_ $cmd "$file" >full_unquoted || fail=1
+  compare full_quoted full_unquoted && fail=1
+done
+
 Exit $fail
-- 
2.56.0


Reply via email to