* src/dd.c (main): Also account for seeking bytes in a few conditions.
* tests/dd/bytes.sh: Add a test case.
* tests/dd/fail-ftruncate-fstat.sh (fail_ftruncate_fstat): New function.
Use it to test seeking on bytes, and make sure fstat and ftruncate are
called.
* NEWS: Mention the bug fix.
Discovered by Anthropic's OSS Scanner, as bug ANT-2026-NLK8TXH1.
---
NEWS | 5 +++
src/dd.c | 7 ++--
tests/dd/bytes.sh | 11 +++++++
tests/dd/fail-ftruncate-fstat.sh | 56 ++++++++++++++++++--------------
4 files changed, 51 insertions(+), 28 deletions(-)
diff --git a/NEWS b/NEWS
index ff06b642f..d4345477a 100644
--- a/NEWS
+++ b/NEWS
@@ -58,6 +58,11 @@ GNU coreutils NEWS -*-
outline -*-
setting the date and time.
[bug introduced in coreutils-9.11]
+ 'dd seek=N' no longer replaces the first N bytes of the file with a hole when
+ N is measured in bytes, i.e., when N ends with a 'B' suffix or
+ oflag=seek_bytes, and when N is smaller than the output block size.
+ [bug introduced with oflag=seek_bytes in coreutils-8.16]
+
'factor' no longer treats inputs requiring the use of arbitrary-precision
integers followed by a non-numeric suffix as zero if the suffix includes
non-space characters, or the number itself when followed by spaces.
diff --git a/src/dd.c b/src/dd.c
index 26382a233..a480d9690 100644
--- a/src/dd.c
+++ b/src/dd.c
@@ -2482,11 +2482,12 @@ main (int argc, char **argv)
else
{
mode_t perms = MODE_RW_UGO;
+ bool const seeking = seek_records || seek_bytes;
int opts
= (output_flags
| (conversions_mask & C_NOCREAT ? 0 : O_CREAT)
| (conversions_mask & C_EXCL ? O_EXCL : 0)
- | (seek_records || (conversions_mask & C_NOTRUNC) ? 0 : O_TRUNC));
+ | (seeking || (conversions_mask & C_NOTRUNC) ? 0 : O_TRUNC));
off_t size;
if ((ckd_mul (&size, seek_records, output_blocksize)
@@ -2501,14 +2502,14 @@ main (int argc, char **argv)
/* Open the output file with *read* access only if we might
need to read to satisfy a 'seek=' request. If we can't read
the file, go ahead with write-only access; it might work. */
- if ((! seek_records
+ if ((! seeking
|| ifd_reopen (STDOUT_FILENO, output_file, O_RDWR | opts, perms) <
0)
&& (ifd_reopen (STDOUT_FILENO, output_file, O_WRONLY | opts, perms)
< 0))
error (EXIT_FAILURE, errno, _("failed to open %s"),
quoteaf (output_file));
- if (seek_records != 0 && !(conversions_mask & C_NOTRUNC))
+ if (seeking && !(conversions_mask & C_NOTRUNC))
{
if (iftruncate (STDOUT_FILENO, size) != 0)
{
diff --git a/tests/dd/bytes.sh b/tests/dd/bytes.sh
index 013cc0006..5490fc9ac 100755
--- a/tests/dd/bytes.sh
+++ b/tests/dd/bytes.sh
@@ -80,4 +80,15 @@ fi
for count in B B1 Bx1 KBB BB KBb KBx x1 1x 1xx1; do
returns_ 1 dd count=$count </dev/null >/dev/null || fail=1
done
+
+printf abcdefghijklnopqrstuvwxyz >exp || framework_failure_
+for seek_arg in seek=12B 'seek=12 oflag=seek_bytes'; do
+ for bs_arg in '' bs=13 obs=13; do
+ printf abcdefghijklm >out &&
+ printf nopqrstuvwxyz >in || framework_failure_
+ dd if=in of=out $bs_arg $seek_arg status=none || fail=1
+ compare exp out || fail=1
+ done
+done
+
Exit $fail
diff --git a/tests/dd/fail-ftruncate-fstat.sh b/tests/dd/fail-ftruncate-fstat.sh
index 70936f712..328bb2852 100755
--- a/tests/dd/fail-ftruncate-fstat.sh
+++ b/tests/dd/fail-ftruncate-fstat.sh
@@ -21,30 +21,36 @@ print_ver_ dd
uses_strace_
getlimits_
-# 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_
-
-strace -o strace.out \
- -e fault=ftruncate:error=EPERM \
- -e fault=fstat:error=EPERM \
- -P out \
- -e quiet=path-resolution \
- dd if=/dev/zero of=out count=1 seek=1 status=none 2>err
-
-ret=$?
-
-# This also implicitly checks for strace invocation issues
-grep -E '^(fstat|ftruncate).*INJECTED' strace.out ||
- skip_ 'ftruncate or fstat are not intercepted'
-
-# After ftruncate fails, we use fstat to get the file type.
-echo "dd: cannot fstat 'out': $EPERM" > 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
+fail_ftruncate_fstat ()
+{
+ ftruncate_fstat_syscalls=ftruncate,fstat,fstat64
+ strace -o strace.out -e fault=$ftruncate_fstat_syscalls:error=EPERM \
+ -P out -e quiet=path-resolution "$@" 2>err
+}
+
+fail_ftruncate_fstat true || skip_ 'cannot intercept ftruncate or fstat'
+
+for seek_arg in seek=1 seek=1B 'seek=1 oflag=seek_bytes'; do
+ # 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_
+
+ fail_ftruncate_fstat dd if=/dev/zero of=out count=1 seek=1B status=none
+ ret=$?
+
+ # Seeking on bytes wouldn't perform call ftruncate in coreutils-9.12
+ # and earlier.
+ grep '^fstat.*INJECTED' strace.out || fail=1
+ grep '^ftruncate.*INJECTED' strace.out || fail=1
+
+ # After ftruncate fails, we use fstat to get the file type.
+ echo "dd: cannot fstat 'out': $EPERM" > 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
+done
Exit $fail
--
2.56.0