From: Collin Funk Date: Wed, 24 Dec 2025 05:45:37 +0000 (-0800) Subject: dd: don't continue copying when ftruncate fails using seek= and of= X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b12593d1249b3369a177bc84c93f54239b965e4a;p=thirdparty%2Fcoreutils.git dd: don't continue copying when ftruncate fails using seek= and of= * src/dd.c (main): Reduce the scope of exit_status. Exit immediately if ftruncate fails. * tests/dd/fail-ftruncate-fstat.sh: New test. * tests/local.mk (all_tests): Add the new test. * NEWS: Mention the bug fix. --- diff --git a/NEWS b/NEWS index b82601680c..97f3640ac2 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,10 @@ GNU coreutils NEWS -*- outline -*- 'date' no longer fails with format directives that return an empty string. [bug introduced in coreutils-9.9] + 'dd seek=N of=FILE' no longer continues copying, overwriting FILE if it + exists, if ftruncate fails. + [bug introduced in coreutils-9.1] + du and ls no longer modify strings returned by getenv. POSIX says this is not portable. [bug introduced in fileutils-4.1.6] diff --git a/src/dd.c b/src/dd.c index 117d619081..120f97e6bc 100644 --- a/src/dd.c +++ b/src/dd.c @@ -2379,7 +2379,6 @@ synchronize_output (void) int main (int argc, char **argv) { - int exit_status; off_t offset; install_signal_handlers (); @@ -2472,20 +2471,17 @@ main (int argc, char **argv) int ftruncate_errno = errno; struct stat stdout_stat; if (ifstat (STDOUT_FILENO, &stdout_stat) != 0) - { - diagnose (errno, _("cannot fstat %s"), quoteaf (output_file)); - exit_status = EXIT_FAILURE; - } + error (EXIT_FAILURE, errno, _("cannot fstat %s"), + quoteaf (output_file)); else if (S_ISREG (stdout_stat.st_mode) || S_ISDIR (stdout_stat.st_mode) || S_TYPEISSHM (&stdout_stat)) { intmax_t isize = size; - diagnose (ftruncate_errno, - _("failed to truncate to %jd bytes" - " in output file %s"), - isize, quoteaf (output_file)); - exit_status = EXIT_FAILURE; + error (EXIT_FAILURE, ftruncate_errno, + _("failed to truncate to %jd bytes" + " in output file %s"), + isize, quoteaf (output_file)); } } } @@ -2494,11 +2490,9 @@ main (int argc, char **argv) start_time = gethrxtime (); next_time = start_time + XTIME_PRECISION; - exit_status = dd_copy (); - + int copy_status = dd_copy (); int sync_status = synchronize_output (); - if (sync_status) - exit_status = sync_status; + int exit_status = copy_status | sync_status; if (max_records == 0 && max_bytes == 0) { diff --git a/tests/dd/fail-ftruncate-fstat.sh b/tests/dd/fail-ftruncate-fstat.sh new file mode 100755 index 0000000000..994051f17f --- /dev/null +++ b/tests/dd/fail-ftruncate-fstat.sh @@ -0,0 +1,76 @@ +#!/bin/sh +# Check that 'dd' does not continue copying if ftruncate and fstat fail. + +# 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_ dd +require_gcc_shared_ + +cat > k.c <<'EOF' || framework_failure_ +#include +#include +#include +#include + +int +ftruncate (int fd, off_t length) +{ + /* Prove that LD_PRELOAD works: create the evidence file "x". */ + fclose (fopen ("x", "w")); + + /* Pretend failure. */ + errno = EPERM; + return -1; +} + +int +fstat (int fd, struct stat *statbuf) +{ + /* Prove that LD_PRELOAD works: create the evidence file "y". */ + fclose (fopen ("y", "w")); + + /* Pretend failure. */ + errno = EPERM; + return -1; +} +EOF + +# Then compile/link it: +gcc_shared_ k.c k.so \ + || framework_failure_ 'failed to build shared library' + +# Setup the file "out" and preserve it's original contents in "exp-out". +yes | head -n 2048 | tr -d '\n' > out || framework_failure_ +cp out exp-out || framework_failure_ + +LD_PRELOAD=$LD_PRELOAD:./k.so dd if=/dev/zero of=out count=1 \ + seek=1 status=none 2>err +ret=$? + +test -f x && test -f y \ + || skip_ "internal test failure: maybe LD_PRELOAD doesn't work?" + +# After ftruncate fails, we use fstat to get the file type. +echo "dd: cannot fstat 'out': Operation not permitted" > exp +compare exp err || fail=1 + +# coreutils 9.1 to 9.9 would mistakenly continue copying after ftruncate +# failed and exit successfully. +test "$ret" = 1 || fail=1 +compare exp-out out || fail=1 + +Exit $fail diff --git a/tests/local.mk b/tests/local.mk index 59aa18adfa..56a37c5242 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -583,6 +583,7 @@ all_tests = \ tests/dd/ascii.sh \ tests/dd/conv-case.sh \ tests/dd/direct.sh \ + tests/dd/fail-ftruncate-fstat.sh \ tests/dd/misc.sh \ tests/dd/no-allocate.sh \ tests/dd/nocache.sh \