From 98f865bcae5588c50f5f78872da1ae8f85152dbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Zaoral?= Date: Wed, 10 Jun 2026 13:12:41 +0200 Subject: [PATCH] unexpand: fix heap overflow when a wide blank overshoots a tab stop Resolves: RHEL-152110 --- coreutils-i18n.patch | 85 +++++++++++++++++++++++++------------------- coreutils.spec | 5 ++- 2 files changed, 52 insertions(+), 38 deletions(-) diff --git a/coreutils-i18n.patch b/coreutils-i18n.patch index 938d276..fb23ea6 100644 --- a/coreutils-i18n.patch +++ b/coreutils-i18n.patch @@ -21,7 +21,7 @@ Subject: [PATCH] coreutils-i18n.patch src/local.mk | 4 +- src/pr.c | 443 ++++++++++++++++++-- src/sort.c | 792 +++++++++++++++++++++++++++++++++--- - src/unexpand.c | 102 ++++- + src/unexpand.c | 104 ++++- tests/Coreutils.pm | 3 + tests/expand/mb.sh | 183 +++++++++ tests/i18n/sort.sh | 29 ++ @@ -33,8 +33,8 @@ Subject: [PATCH] coreutils-i18n.patch tests/pr/pr-tests.pl | 49 +++ tests/sort/sort-merge.pl | 42 ++ tests/sort/sort.pl | 40 +- - tests/unexpand/mb.sh | 179 ++++++++ - 30 files changed, 3606 insertions(+), 196 deletions(-) + tests/unexpand/mb.sh | 188 +++++++++ + 30 files changed, 3616 insertions(+), 197 deletions(-) create mode 100644 lib/mbchar.c create mode 100644 lib/mbchar.h create mode 100644 lib/mbfile.c @@ -47,7 +47,7 @@ Subject: [PATCH] coreutils-i18n.patch create mode 100644 tests/unexpand/mb.sh diff --git a/bootstrap.conf b/bootstrap.conf -index 126e1e80..b4ccebf2 100644 +index 126e1e8..b4ccebf 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -163,6 +163,8 @@ gnulib_modules=" @@ -60,7 +60,7 @@ index 126e1e80..b4ccebf2 100644 mbrtoc32 mbrtowc diff --git a/configure.ac b/configure.ac -index 9cb6ee14..1131ce35 100644 +index 9cb6ee1..1131ce3 100644 --- a/configure.ac +++ b/configure.ac @@ -504,6 +504,12 @@ fi @@ -77,7 +77,7 @@ index 9cb6ee14..1131ce35 100644 if test $gl_cv_sys_tiocgwinsz_needs_termios_h = no && \ diff --git a/lib/linebuffer.h b/lib/linebuffer.h -index ae0d55dd..5bf53503 100644 +index ae0d55d..5bf5350 100644 --- a/lib/linebuffer.h +++ b/lib/linebuffer.h @@ -22,6 +22,11 @@ @@ -104,7 +104,7 @@ index ae0d55dd..5bf53503 100644 /* Initialize linebuffer LINEBUFFER for use. */ diff --git a/lib/mbchar.c b/lib/mbchar.c new file mode 100644 -index 00000000..d94b7c33 +index 0000000..d94b7c3 --- /dev/null +++ b/lib/mbchar.c @@ -0,0 +1,23 @@ @@ -133,7 +133,7 @@ index 00000000..d94b7c33 +#include "mbchar.h" diff --git a/lib/mbchar.h b/lib/mbchar.h new file mode 100644 -index 00000000..c06ef11b +index 0000000..c06ef11 --- /dev/null +++ b/lib/mbchar.h @@ -0,0 +1,373 @@ @@ -512,7 +512,7 @@ index 00000000..c06ef11b +#endif /* _MBCHAR_H */ diff --git a/lib/mbfile.c b/lib/mbfile.c new file mode 100644 -index 00000000..8d2957b5 +index 0000000..8d2957b --- /dev/null +++ b/lib/mbfile.c @@ -0,0 +1,20 @@ @@ -538,7 +538,7 @@ index 00000000..8d2957b5 +#include "mbfile.h" diff --git a/lib/mbfile.h b/lib/mbfile.h new file mode 100644 -index 00000000..1ea1358b +index 0000000..1ea1358 --- /dev/null +++ b/lib/mbfile.h @@ -0,0 +1,261 @@ @@ -805,7 +805,7 @@ index 00000000..1ea1358b +#endif /* _MBFILE_H */ diff --git a/m4/mbchar.m4 b/m4/mbchar.m4 new file mode 100644 -index 00000000..471e8c45 +index 0000000..471e8c4 --- /dev/null +++ b/m4/mbchar.m4 @@ -0,0 +1,13 @@ @@ -824,7 +824,7 @@ index 00000000..471e8c45 +]) diff --git a/m4/mbfile.m4 b/m4/mbfile.m4 new file mode 100644 -index 00000000..83068a99 +index 0000000..83068a9 --- /dev/null +++ b/m4/mbfile.m4 @@ -0,0 +1,14 @@ @@ -843,7 +843,7 @@ index 00000000..83068a99 + : +]) diff --git a/src/cut.c b/src/cut.c -index 061e09c3..6d104259 100644 +index 061e09c..6d10425 100644 --- a/src/cut.c +++ b/src/cut.c @@ -27,6 +27,11 @@ @@ -1503,7 +1503,7 @@ index 061e09c3..6d104259 100644 if (have_read_stdin && fclose (stdin) == EOF) diff --git a/src/expand-common.c b/src/expand-common.c -index c95998dc..d4386fec 100644 +index c95998d..d4386fe 100644 --- a/src/expand-common.c +++ b/src/expand-common.c @@ -19,6 +19,7 @@ @@ -1635,7 +1635,7 @@ index c95998dc..d4386fec 100644 to the list of tab stops. */ extern void diff --git a/src/expand-common.h b/src/expand-common.h -index 1a57108e..60256522 100644 +index 1a57108..6025652 100644 --- a/src/expand-common.h +++ b/src/expand-common.h @@ -25,6 +25,18 @@ extern size_t max_column_width; @@ -1658,7 +1658,7 @@ index 1a57108e..60256522 100644 extern void add_tab_stop (uintmax_t tabval); diff --git a/src/expand.c b/src/expand.c -index a6176a97..60b1b8ee 100644 +index a6176a9..60b1b8e 100644 --- a/src/expand.c +++ b/src/expand.c @@ -38,6 +38,9 @@ @@ -1814,7 +1814,7 @@ index a6176a97..60b1b8ee 100644 } diff --git a/src/fold.c b/src/fold.c -index 941ad11c..2b119815 100644 +index 941ad11..2b11981 100644 --- a/src/fold.c +++ b/src/fold.c @@ -23,10 +23,32 @@ @@ -2218,7 +2218,7 @@ index 941ad11c..2b119815 100644 case 's': /* Break at word boundaries. */ diff --git a/src/local.mk b/src/local.mk -index 96ee941c..8fdb8fc3 100644 +index 96ee941..8fdb8fc 100644 --- a/src/local.mk +++ b/src/local.mk @@ -450,8 +450,8 @@ src_base32_CPPFLAGS = -DBASE_TYPE=32 $(AM_CPPFLAGS) @@ -2233,7 +2233,7 @@ index 96ee941c..8fdb8fc3 100644 src_wc_SOURCES = src/wc.c if USE_AVX2_WC_LINECOUNT diff --git a/src/pr.c b/src/pr.c -index 09c6fa8a..7552b629 100644 +index 09c6fa8..7552b62 100644 --- a/src/pr.c +++ b/src/pr.c @@ -312,6 +312,24 @@ @@ -3004,7 +3004,7 @@ index 09c6fa8a..7552b629 100644 looking for more options and printing the next batch of files. diff --git a/src/sort.c b/src/sort.c -index 09634197..dea94575 100644 +index 0963419..dea9457 100644 --- a/src/sort.c +++ b/src/sort.c @@ -29,6 +29,14 @@ @@ -4082,7 +4082,7 @@ index 09634197..dea94575 100644 break; diff --git a/src/unexpand.c b/src/unexpand.c -index aca67dd7..dacded6d 100644 +index aca67dd..ae897d5 100644 --- a/src/unexpand.c +++ b/src/unexpand.c @@ -39,6 +39,9 @@ @@ -4194,7 +4194,7 @@ index aca67dd7..dacded6d 100644 if (blank) { -@@ -178,16 +236,16 @@ unexpand (void) +@@ -178,30 +236,31 @@ unexpand (void) if (next_tab_column < column) error (EXIT_FAILURE, 0, _("input line is too long")); @@ -4212,9 +4212,10 @@ index aca67dd7..dacded6d 100644 - column++; + column += mb_width (c); - if (! (prev_blank && column == next_tab_column)) +- if (! (prev_blank && column == next_tab_column)) ++ if (! (prev_blank && column >= next_tab_column)) { -@@ -195,13 +253,14 @@ unexpand (void) + /* It is not yet known whether the pending blanks will be replaced by tabs. */ if (column == next_tab_column) one_blank_before_tab_stop = true; @@ -4287,7 +4288,7 @@ index aca67dd7..dacded6d 100644 } diff --git a/tests/Coreutils.pm b/tests/Coreutils.pm -index 18e7bea8..24a141b1 100644 +index 18e7bea..24a141b 100644 --- a/tests/Coreutils.pm +++ b/tests/Coreutils.pm @@ -269,6 +269,9 @@ sub run_tests ($$$$$) @@ -4302,7 +4303,7 @@ index 18e7bea8..24a141b1 100644 warn "$program_name: $test_name: test name is too long (> $max)\n"; diff --git a/tests/expand/mb.sh b/tests/expand/mb.sh new file mode 100644 -index 00000000..6d6497a3 +index 0000000..6d6497a --- /dev/null +++ b/tests/expand/mb.sh @@ -0,0 +1,183 @@ @@ -4491,7 +4492,7 @@ index 00000000..6d6497a3 +Exit $fail diff --git a/tests/i18n/sort.sh b/tests/i18n/sort.sh new file mode 100644 -index 00000000..26c95de9 +index 0000000..26c95de --- /dev/null +++ b/tests/i18n/sort.sh @@ -0,0 +1,29 @@ @@ -4525,7 +4526,7 @@ index 00000000..26c95de9 + +Exit $fail diff --git a/tests/local.mk b/tests/local.mk -index d4546a0f..3bd74532 100644 +index d4546a0..3bd7453 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -388,6 +388,8 @@ all_tests = \ @@ -4554,7 +4555,7 @@ index d4546a0f..3bd74532 100644 # See tests/factor/create-test.sh. diff --git a/tests/misc/expand.pl b/tests/misc/expand.pl -index 11f3fc4b..d609a2cb 100755 +index 11f3fc4..d609a2c 100755 --- a/tests/misc/expand.pl +++ b/tests/misc/expand.pl @@ -27,6 +27,15 @@ my $prog = 'expand'; @@ -4621,7 +4622,7 @@ index 11f3fc4b..d609a2cb 100755 my $verbose = $ENV{VERBOSE}; diff --git a/tests/misc/fold.pl b/tests/misc/fold.pl -index 00b43624..7d51bea5 100755 +index 00b4362..7d51bea 100755 --- a/tests/misc/fold.pl +++ b/tests/misc/fold.pl @@ -20,9 +20,18 @@ use strict; @@ -4695,7 +4696,7 @@ index 00b43624..7d51bea5 100755 exit $fail; diff --git a/tests/misc/sort-mb-tests.sh b/tests/misc/sort-mb-tests.sh new file mode 100644 -index 00000000..11836baa +index 0000000..11836ba --- /dev/null +++ b/tests/misc/sort-mb-tests.sh @@ -0,0 +1,45 @@ @@ -4745,7 +4746,7 @@ index 00000000..11836baa + +Exit $fail diff --git a/tests/misc/unexpand.pl b/tests/misc/unexpand.pl -index 76bcbd4d..59eb8199 100755 +index 76bcbd4..59eb819 100755 --- a/tests/misc/unexpand.pl +++ b/tests/misc/unexpand.pl @@ -27,6 +27,14 @@ my $limits = getlimits (); @@ -4802,7 +4803,7 @@ index 76bcbd4d..59eb8199 100755 my $verbose = $ENV{VERBOSE}; diff --git a/tests/pr/pr-tests.pl b/tests/pr/pr-tests.pl -index 6b34e0b6..34b4aeb0 100755 +index 6b34e0b..34b4aeb 100755 --- a/tests/pr/pr-tests.pl +++ b/tests/pr/pr-tests.pl @@ -24,6 +24,15 @@ use strict; @@ -4871,7 +4872,7 @@ index 6b34e0b6..34b4aeb0 100755 my $verbose = $ENV{VERBOSE}; diff --git a/tests/sort/sort-merge.pl b/tests/sort/sort-merge.pl -index 89eed0c6..b855d738 100755 +index 89eed0c..b855d73 100755 --- a/tests/sort/sort-merge.pl +++ b/tests/sort/sort-merge.pl @@ -26,6 +26,15 @@ my $prog = 'sort'; @@ -4931,7 +4932,7 @@ index 89eed0c6..b855d738 100755 my $verbose = $ENV{VERBOSE}; diff --git a/tests/sort/sort.pl b/tests/sort/sort.pl -index d49f65f6..ebba9255 100755 +index d49f65f..ebba925 100755 --- a/tests/sort/sort.pl +++ b/tests/sort/sort.pl @@ -24,10 +24,15 @@ my $prog = 'sort'; @@ -5000,10 +5001,10 @@ index d49f65f6..ebba9255 100755 my $verbose = $ENV{VERBOSE}; diff --git a/tests/unexpand/mb.sh b/tests/unexpand/mb.sh new file mode 100644 -index 00000000..89cac17b +index 0000000..5229b7c --- /dev/null +++ b/tests/unexpand/mb.sh -@@ -0,0 +1,179 @@ +@@ -0,0 +1,188 @@ +#!/bin/sh + +# Copyright (C) 2012-2015 Free Software Foundation, Inc. @@ -5182,6 +5183,16 @@ index 00000000..89cac17b + test "$ret" = 1 || test "$ret" = 0 || { cat err; fail=1; } +done + ++# A blank whose display width exceeds the tab distance must not overrun ++# the pending-blank buffer. With -t1 every column is a tab stop, so a ++# width-2 ideographic space steps over the stop without landing on it; ++# the run of blanks then grew pending_blank without bound. ++ideo_space=$(env printf '\u3000') ++{ yes "$ideo_space" | head -n 40000 | tr -d '\n'; echo; } | ++ unexpand -t1 >out 2>err; ret=$? ++test "$ret" = 0 || { cat err; fail=1; } ++ +Exit $fail -- 2.54.0 + diff --git a/coreutils.spec b/coreutils.spec index d4d85f9..e1c2497 100644 --- a/coreutils.spec +++ b/coreutils.spec @@ -1,7 +1,7 @@ Summary: A set of basic GNU tools commonly used in shell scripts Name: coreutils Version: 9.5 -Release: 11%{?dist} +Release: 12%{?dist} # some used parts of gnulib are under various variants of LGPL License: GPL-3.0-or-later AND GFDL-1.3-no-invariants-or-later AND LGPL-2.1-or-later AND LGPL-3.0-or-later Url: https://www.gnu.org/software/coreutils/ @@ -309,6 +309,9 @@ rm -f $RPM_BUILD_ROOT%{_infodir}/dir %license COPYING %changelog +* Wed Jun 10 2026 Lukáš Zaoral - 9.5-12 +- unexpand: fix heap overflow when a wide blank overshoots a tab stop (RHEL-152110) + * Tue Jun 09 2026 Lukáš Zaoral - 9.5-11 - Fix cp --preserve=mode for ACLs (RHEL-132191)