From: Ismail Ramzi <[email protected]>

When FROM contained the digits a-f, next_file_name() derived the
suffix index with c - '0', giving values up to 54 for a 16 character
alphabet.  Incrementing the suffix then read past the end of the
alphabet string and used those bytes in the output file names,
e.g., 'split --hex-suffixes=f' created x0f, x0s, x0u.

* src/split.c (next_file_name): Map start value characters through
the suffix alphabet rather than assuming decimal digits.
(main): Reject a start value not valid for the final suffix alphabet,
as can happen with --hex-suffixes=a --numeric-suffixes.
(set_suffix_length): Determine width using base 16 for hex suffixes.
* tests/split/numeric.sh: Add test cases.
* NEWS: Mention the bug fix.

Link: https://github.com/coreutils/coreutils/pull/404
Also discovered by Anthropic's OSS Scanner, as bug ANT-2026-DWADVADP.
---
 NEWS                   |  5 +++++
 src/split.c            | 18 +++++++++++++++---
 tests/split/numeric.sh | 23 +++++++++++++++++++++++
 3 files changed, 43 insertions(+), 3 deletions(-)

diff --git a/NEWS b/NEWS
index 6b4ed1d7b..6fddbaaa3 100644
--- a/NEWS
+++ b/NEWS
@@ -148,6 +148,11 @@ GNU coreutils NEWS                                    -*- 
outline -*-
   environment.
   [bug introduced in coreutils-9.9]
 
+  'split --hex-suffixes=FROM' now generates consecutive suffixes when FROM
+  contains any of the digits a to f.  Previously it read beyond the suffix
+  alphabet, giving arbitrary and repeated file names, thus losing output.
+  [bug introduced with the --hex-suffixes option in coreutils-8.28]
+
   'stty' again rejects empty speed arguments.
   Previously such arguments were accepted as value 0.
   [bug introduced in coreutils-9.8]
diff --git a/src/split.c b/src/split.c
index fd3dae24b..ec9160114 100644
--- a/src/split.c
+++ b/src/split.c
@@ -186,10 +186,11 @@ set_suffix_length (intmax_t n_units, enum Split_type 
split_type)
       || split_type == type_rr)
     {
       intmax_t n_units_end = n_units - 1;
+      idx_t alphabet_len = strlen (suffix_alphabet);
       if (numeric_suffix_start)
         {
           intmax_t n_start;
-          strtol_error e = xstrtoimax (numeric_suffix_start, NULL, 10,
+          strtol_error e = xstrtoimax (numeric_suffix_start, NULL, 
alphabet_len,
                                        &n_start, "");
           if (e == LONGINT_OK && n_start < n_units)
             {
@@ -202,7 +203,6 @@ set_suffix_length (intmax_t n_units, enum Split_type 
split_type)
             }
 
         }
-      idx_t alphabet_len = strlen (suffix_alphabet);
       do
         suffix_length_needed++;
       while (n_units_end /= alphabet_len);
@@ -480,7 +480,8 @@ new_name:
           /* Update the suffix index.  */
           idx_t *sufindex_end = sufindex + suffix_length;
           while (i-- != 0)
-            *--sufindex_end = numeric_suffix_start[i] - '0';
+            *--sufindex_end = (strchr (suffix_alphabet, 
numeric_suffix_start[i])
+                               - suffix_alphabet);
         }
 
 #if ! _POSIX_NO_TRUNC && HAVE_PATHCONF && defined _PC_NAME_MAX
@@ -1668,6 +1669,17 @@ main (int argc, char **argv)
       usage (EXIT_FAILURE);
     }
 
+  /* A start value given with --hex-suffixes may be invalid
+     for a later --numeric-suffixes.  */
+  if (numeric_suffix_start
+      && (strlen (numeric_suffix_start)
+          != strspn (numeric_suffix_start, suffix_alphabet)))
+    {
+      error (0, 0, _("%s: invalid start value for numerical suffix"),
+             quote (numeric_suffix_start));
+      usage (EXIT_FAILURE);
+    }
+
   /* Check that the suffix length is large enough for the numerical
      suffix start value.  */
   if (numeric_suffix_start && strlen (numeric_suffix_start) > suffix_length)
diff --git a/tests/split/numeric.sh b/tests/split/numeric.sh
index 9ae6cb280..204ab98e5 100755
--- a/tests/split/numeric.sh
+++ b/tests/split/numeric.sh
@@ -48,4 +48,27 @@ for mode in 'numeric' 'hex'; do
   returns_ 1 split --$mode-suffixes=one in 2> /dev/null || fail=1
 done
 
+# Check hexadecimal start values that are not decimal digits
+rm -f x?? || framework_failure_
+split --hex-suffixes=f --lines=2 in || fail=1
+compare exp-0 x0f || fail=1
+compare exp-1 x10 || fail=1
+compare exp-2 x11 || fail=1
+
+# A hexadecimal start value is invalid for a later --numeric-suffixes
+returns_ 1 split --hex-suffixes=a --numeric-suffixes in 2> /dev/null || fail=1
+
+# Only the final alphabet needs to accommodate the retained start value.
+split --hex-suffixes=a -d -x --lines=2 in || fail=1
+compare exp-0 x0a || fail=1
+compare exp-1 x0b || fail=1
+compare exp-2 x0c || fail=1
+
+# Interpret the start value as hex when determining the suffix length.
+# 0x10 + 241 - 1 = 0x100, which requires three digits.
+split --hex-suffixes=10 --number=r/241 in || fail=1
+test -f x010 && test -f x100 || fail=1
+returns_ 1 split -a2 --hex-suffixes=10 --number=r/241 in 2>/dev/null \
+  || fail=1
+
 Exit $fail
-- 
2.56.0


Reply via email to