From 7ad749886cddc76860bd8cfc38a408032a5e4c99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draig=20Brady?= Date: Wed, 29 Mar 2023 15:29:52 +0100 Subject: wc: diagnose overflow of total counts * src/wc.c (wc): Use INT_ADD_WRAPV() to detect overflow. (main): Upon overflow, saturate the total, print a diagnostic, and set exit status. * tests/misc/wc-total.sh: Add a test case, which operates on BTRFS and 64 bit systems at least. Reported at https://bugs.debian.org/1027100 --- NEWS | 3 +++ src/wc.c | 51 +++++++++++++++++++++++++++++++++++++++++++------- tests/misc/wc-total.sh | 17 +++++++++++++++++ 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index bade99043..f53adab6f 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,9 @@ GNU coreutils NEWS -*- outline -*- This also applies to cksum, sha*sum, and b2sum. [bug introduced in coreutils-9.2] + wc will now diagnose if any total counts have overflowed. + [This bug was present in "the beginning".] + * Noteworthy changes in release 9.2 (2023-03-20) [stable] diff --git a/src/wc.c b/src/wc.c index 5f3ef6eee..801396a15 100644 --- a/src/wc.c +++ b/src/wc.c @@ -78,6 +78,10 @@ static uintmax_t total_lines; static uintmax_t total_words; static uintmax_t total_chars; static uintmax_t total_bytes; +static uintmax_t total_lines_overflow; +static uintmax_t total_words_overflow; +static uintmax_t total_chars_overflow; +static uintmax_t total_bytes_overflow; static uintmax_t max_line_length; /* Which counts to print. */ @@ -703,10 +707,16 @@ wc (int fd, char const *file_x, struct fstatus *fstatus, off_t current_pos) if (total_mode != total_only) write_counts (lines, words, chars, bytes, linelength, file_x); - total_lines += lines; - total_words += words; - total_chars += chars; - total_bytes += bytes; + + if (INT_ADD_WRAPV (total_lines, lines, &total_lines)) + total_lines_overflow = true; + if (INT_ADD_WRAPV (total_words, words, &total_words)) + total_words_overflow = true; + if (INT_ADD_WRAPV (total_chars, chars, &total_chars)) + total_chars_overflow = true; + if (INT_ADD_WRAPV (total_bytes, bytes, &total_bytes)) + total_bytes_overflow = true; + if (linelength > max_line_length) max_line_length = linelength; @@ -1022,9 +1032,36 @@ main (int argc, char **argv) if (total_mode != total_never && (total_mode != total_auto || 1 < argv_iter_n_args (ai))) - write_counts (total_lines, total_words, total_chars, total_bytes, - max_line_length, - total_mode != total_only ? _("total") : NULL); + { + if (total_lines_overflow) + { + total_lines = UINTMAX_MAX; + error (0, EOVERFLOW, _("total lines")); + ok = false; + } + if (total_words_overflow) + { + total_words = UINTMAX_MAX; + error (0, EOVERFLOW, _("total words")); + ok = false; + } + if (total_chars_overflow) + { + total_chars = UINTMAX_MAX; + error (0, EOVERFLOW, _("total characters")); + ok = false; + } + if (total_bytes_overflow) + { + total_bytes = UINTMAX_MAX; + error (0, EOVERFLOW, _("total bytes")); + ok = false; + } + + write_counts (total_lines, total_words, total_chars, total_bytes, + max_line_length, + total_mode != total_only ? _("total") : NULL); + } argv_iter_free (ai); diff --git a/tests/misc/wc-total.sh b/tests/misc/wc-total.sh index 4848111cd..113b35504 100755 --- a/tests/misc/wc-total.sh +++ b/tests/misc/wc-total.sh @@ -18,6 +18,8 @@ . "${srcdir=.}/tests/init.sh"; path_prepend_ ./src print_ver_ wc +require_sparse_support_ # for 'truncate --size=$BIG' +getlimits_ printf '%s\n' '2' > 2b || framework_failure_ printf '%s\n' '2 words' > 2w || framework_failure_ @@ -40,4 +42,19 @@ compare exp out || fail=1 wc --total=always 2b > out || fail=1 test "$(wc -l < out)" = 2 || fail=1 +if truncate -s 2E big; then + if test "$UINTMAX_MAX" = '18446744073709551615'; then + # Ensure overflow is diagnosed + returns_ 1 wc --total=only -c big big big big big big big big \ + > out || fail=1 + + # Ensure total is saturated + printf '%s\n' "$UINTMAX_MAX" > exp || framework_failure_ + compare exp out || fail=1 + + # Ensure overflow is ignored if totals not shown + wc --total=never -c big big big big big big big big || fail=1 + fi +fi + Exit $fail -- cgit v1.2.1