From: Ismail Ramzi <[email protected]>

A single character --section-delimiter is completed with an implied ':'
by writing through section_del, which is assumed to still point at the
static DEFAULT_SECTION_DELIMITERS buffer.  After an earlier -d with zero
or more than one character, section_del points at that argument instead,
so the write goes into the earlier argv string and past its end.
In a UTF-8 locale 'nl -d "" FILE -d x' corrupts the FILE operand,
and in the C locale 'nl -d foo -d x' uses the delimiter "xoo".

* src/nl.c (main): Point section_del at the static buffer before
building the single character delimiter.
* tests/nl/nl.sh, tests/nl/multibyte.sh: Add test cases.
* NEWS: Mention the bug fix.
Bug present since coreutils 9.0.

Link: https://github.com/coreutils/coreutils/pull/403
---
 NEWS                  |  5 +++++
 src/nl.c              | 12 ++++++++++--
 tests/nl/multibyte.sh | 11 +++++++++++
 tests/nl/nl.sh        | 11 +++++++++++
 4 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/NEWS b/NEWS
index 7071d6878..aee2afea0 100644
--- a/NEWS
+++ b/NEWS
@@ -107,6 +107,11 @@ GNU coreutils NEWS                                    -*- 
outline -*-
   'nl -d ""' no longer mistakenly treats empty lines as headers.
   [bugs introduced in coreutils-9.11]
 
+  'nl -d C' now works when a longer --section-delimiter was given earlier
+  on the command line.  Previously the new delimiter was written through
+  the earlier argument, which could corrupt following command line arguments.
+  [bug introduced in coreutils-9.0]
+
   od now exits gracefully when offsets overflow integer limits.
   [This bug was present in "the beginning".]
 
diff --git a/src/nl.c b/src/nl.c
index 0f92cfef6..826ca4e48 100644
--- a/src/nl.c
+++ b/src/nl.c
@@ -589,14 +589,22 @@ main (int argc, char **argv)
               for (; p < lim && n_chars < 2; ++n_chars)
                   p += mcel_scan (p, lim).len;
               if (n_chars == 1)
-                memcpy (mempcpy (section_del, optarg, len),  ":", sizeof ":");
+                {
+                  /* Build the delimiter in the static buffer, as
+                     section_del may point to a previous -d argument.  */
+                  section_del = DEFAULT_SECTION_DELIMITERS;
+                  memcpy (mempcpy (section_del, optarg, len), ":", sizeof ":");
+                }
               else
                 section_del = optarg;
             }
           else
             {
               if (len == 1)
-                *section_del = *optarg;
+                {
+                  section_del = DEFAULT_SECTION_DELIMITERS;
+                  *section_del = *optarg;
+                }
               else
                 section_del = optarg;
             }
diff --git a/tests/nl/multibyte.sh b/tests/nl/multibyte.sh
index 3ad307b63..f7a6ce2bf 100755
--- a/tests/nl/multibyte.sh
+++ b/tests/nl/multibyte.sh
@@ -54,4 +54,15 @@ test_nl_multibyte '\uB250\uB250' '\uB250\uB250' || fail=1
 test_nl_multibyte '\uB250\uB250\uB250' '\uB250\uB250\uB250' || fail=1
 test_nl_multibyte "$(bad_unicode)" "$(bad_unicode)" || fail=1
 
+# Ensure a single character delimiter is handled after a longer one.
+# coreutils 9.11 and 9.12 wrote the new delimiter through the previous
+# argument, corrupting the following command line arguments.
+{
+  export LC_ALL="$LOCALE_FR_UTF8"
+  env printf '\uB250:\uB250:\uB250:\na\n\uB250:\uB250:\nb\n\uB250:\nc\n' > inp 
\
+    || framework_failure_
+  nl -p -ha -fa -d '' inp -d "$(env printf '\uB250')" > out || fail=1
+}
+compare exp out || fail=1
+
 Exit $fail
diff --git a/tests/nl/nl.sh b/tests/nl/nl.sh
index 88b0785e9..c36047270 100755
--- a/tests/nl/nl.sh
+++ b/tests/nl/nl.sh
@@ -110,6 +110,17 @@ cat <<EOF > exp
 EOF
 compare exp out || fail=1
 
+# Ensure a single char delimiter is handled after a longer one.
+# coreutils 9.0 to 9.12 wrote the new delimiter through the previous argument.
+printf '%s\n' a x:x: c > in.txt || framework_failure_
+nl -d foo -d x in.txt > out || fail=1
+cat <<EOF > exp
+     1 a
+
+     1 c
+EOF
+compare exp out || fail=1
+
 # Test that all files are processed.
 echo a > file1
 echo b > file2
-- 
2.56.0


Reply via email to