From 98e5418a38fa8e944d5b694ba2aa0c165092e63b Mon Sep 17 00:00:00 2001 From: Collin Funk Date: Mon, 15 Sep 2025 20:53:23 -0700 Subject: [PATCH] fold: fix out of bounds write with zero width characters * src/fold.c (fold_file): Prefer putchar ('\n') to copying characters. If we do not have room in the output buffer print it since it is not a full line of text. * tests/fold/fold-zero-width.sh: New test case. * tests/local.mk (all_tests): Add it. --- src/fold.c | 15 +++++++--- tests/fold/fold-zero-width.sh | 55 +++++++++++++++++++++++++++++++++++ tests/local.mk | 1 + 3 files changed, 67 insertions(+), 4 deletions(-) create mode 100755 tests/fold/fold-zero-width.sh diff --git a/src/fold.c b/src/fold.c index b90bc7d80a..1908659630 100644 --- a/src/fold.c +++ b/src/fold.c @@ -192,9 +192,8 @@ fold_file (char const *filename, size_t width) } if (g.ch == '\n') { - memcpy (line_out + offset_out, p, g.len); - offset_out += g.len; fwrite (line_out, sizeof (char), offset_out, stdout); + putchar ('\n'); column = offset_out = 0; continue; } @@ -249,17 +248,25 @@ fold_file (char const *filename, size_t width) if (offset_out == 0) { - memcpy (line_out + offset_out, p, g.len); + memcpy (line_out, p, g.len); offset_out += g.len; continue; } - line_out[offset_out++] = '\n'; fwrite (line_out, sizeof (char), offset_out, stdout); + putchar ('\n'); column = offset_out = 0; goto rescan; } + /* This can occur if we have read characters with a width of + zero. */ + if (sizeof line_out <= offset_out + g.len) + { + fwrite (line_out, sizeof (char), offset_out, stdout); + offset_out = 0; + } + memcpy (line_out + offset_out, p, g.len); offset_out += g.len; } diff --git a/tests/fold/fold-zero-width.sh b/tests/fold/fold-zero-width.sh new file mode 100755 index 0000000000..a0d7e3fe61 --- /dev/null +++ b/tests/fold/fold-zero-width.sh @@ -0,0 +1,55 @@ +#!/bin/sh +# Test fold with zero width characters. + +# Copyright (C) 2025 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ fold printf +getlimits_ + +# Make sure we do not overflow the buffer. +IO_BUFSIZE_TIMES2=$(($IO_BUFSIZE * 2)) + +# Fold counts by columns by default. +head -c $IO_BUFSIZE_TIMES2 /dev/zero | fold > out || fail=1 +test $(cat out | wc -l) -eq 0 || fail=1 + +# Check that zero width characters are counted with --characters. +head -c $IO_BUFSIZE_TIMES2 /dev/zero | fold --characters > out || fail=1 +test $(cat out | wc -l) -eq $(($IO_BUFSIZE_TIMES2 / 80)) || fail=1 + +test "$LOCALE_FR_UTF8" != none || skip_ "French UTF-8 locale not available" + +LC_ALL=$LOCALE_FR_UTF8 +export LC_ALL + +# Same thing, but using U+200B ZERO WIDTH SPACE. +yes $(env printf '\u200B') | head -n $IO_BUFSIZE_TIMES2 | tr -d '\n' > inp + +fold inp > out || fail=1 +test $(cat out | wc -l) -eq 0 || fail=1 + +fold --characters inp > out || fail=1 +test $(cat out | wc -l) -eq $(($IO_BUFSIZE_TIMES2 / 80)) || fail=1 + +# Ensure bounded memory operation. +vm=$(get_min_ulimit_v_ fold /dev/null) && { + head -c $IO_BUFSIZE_TIMES2 /dev/zero | tr -d '\n' \ + | (ulimit -v $(($vm+8000)) && fold 2>err) | head || fail=1 + compare /dev/null err || fail=1 +} + +Exit $fail diff --git a/tests/local.mk b/tests/local.mk index 67a919e841..4aa199a197 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -330,6 +330,7 @@ all_tests = \ tests/fold/fold-characters.sh \ tests/fold/fold-nbsp.sh \ tests/fold/fold-spaces.sh \ + tests/fold/fold-zero-width.sh \ tests/fold/fold.pl \ tests/groups/groups-dash.sh \ tests/groups/groups-process-all.sh \ -- 2.47.3