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
