Currently, the following occurs:
$ od -v -t x1 -j $((INTMAX_MAX - 1)) /dev/zero
777777777777777777776 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Segmentation fault (core dumped) od -v -t x1 -j $((INTMAX_MAX - 1))
/dev/zero
This is because the signed integer overflows, becoming negative, before
being used in format_address_std to create a printable string.
The bug was "introduced" by commit 35d9bf691 (od: prefer idx_t to
size_t, 2025-06-24), but the previous behavior would still be a bit
wonky. That is, the offset would eventually overflow, but since it was
unsigned, it would wrap around and start printing from address zero
again.
For that reason, I marked the specific instance invocation as a bug, but
mentioned the general improvement as well. I'm not 100% sure that is the
best way to document it, though.
-- 8< --
* src/od.c (format_address_std): Add an affirm call to check that the
address is positive or equal to zero, which would have caused a
descriptive assertion in this case instead of a SIGSEGV.
(offset_add): New function.
(dump): Use it to diagnose overflows.
(dump_strings): Likewise.
* NEWS: Mention the bug fix and improvement.
Discovered by Anthropic's OSS Scanner, as bug ANT-2026-EZXPB4TK.
---
NEWS | 6 ++++++
src/od.c | 19 ++++++++++++++++---
tests/od/od-j.sh | 17 +++++++++++++++++
3 files changed, 39 insertions(+), 3 deletions(-)
diff --git a/NEWS b/NEWS
index bbb072cb0..588ab5f32 100644
--- a/NEWS
+++ b/NEWS
@@ -51,6 +51,10 @@ GNU coreutils NEWS -*-
outline -*-
just-moved FIFO.
[bug introduced with SELinux support in coreutils-8.22]
+ 'od -v -j OFFSET /dev/zero' no longer crashes when OFFSET is equal to or
close
+ to INTMAX_MAX.
+ [bug introduced in coreutils-9.8]
+
'realpath' without -s, or 'readlink' with -e,-f,-m no longer
loop infinitely when resolving a symbolic link which
resolves to a path beginning with the symbolic link itself.
@@ -140,6 +144,8 @@ GNU coreutils NEWS -*-
outline -*-
'od' allocates half the memory when -v,--output-duplicates is specified.
+ 'od' better detects and reports offset overflows.
+
'tail' ensures prompt output when inotify is not being used, and when
initial reads finish, but subsequent reads might block, which was seen
e.g. with fifos on NetBSD 10.
diff --git a/src/od.c b/src/od.c
index 469e788cc..25ac0cd54 100644
--- a/src/od.c
+++ b/src/od.c
@@ -1242,6 +1242,8 @@ format_address_std (intmax_t address, char c)
char *p = buf + sizeof buf;
char const *pbound;
+ affirm (0 <= address);
+
*--p = '\0';
*--p = c;
pbound = p - address_pad_len;
@@ -1499,6 +1501,17 @@ parse_old_offset (char *str, intmax_t *offset)
return s_err == LONGINT_OK;
}
+/* Add COUNT to OFFSET, and return the result.
+ If an overflow occurs, exit the program with an error message. */
+
+static intmax_t
+offset_add (intmax_t offset, intmax_t const count)
+{
+ if (ckd_add (&offset, offset, count))
+ error (EXIT_FAILURE, 0, _("offset overflow"));
+ return offset;
+}
+
/* Read a chunk of size BYTES_PER_BLOCK from the input files, write the
formatted block to standard output, and repeat until the specified
maximum number of bytes has been read or until all input has been
@@ -1542,7 +1555,7 @@ dump (void)
block[!idx], block[idx]);
if (ferror (stdout))
ok = false;
- current_offset += n_bytes_read;
+ current_offset = offset_add (current_offset, n_bytes_read);
idx = !idx;
}
while (ok);
@@ -1558,7 +1571,7 @@ dump (void)
memset (block[idx] + n_bytes_read, 0, bytes_to_write - n_bytes_read);
write_block (current_offset, n_bytes_read, block[!idx], block[idx]);
- current_offset += n_bytes_read;
+ current_offset = offset_add (current_offset, n_bytes_read);
}
if (ok || current_offset)
@@ -1606,7 +1619,7 @@ dump_strings (void)
free (buf);
return ok;
}
- address++;
+ address = offset_add (address, 1);
buf[i++] = c;
if (c == '\0')
break; /* Print this string. */
diff --git a/tests/od/od-j.sh b/tests/od/od-j.sh
index 98e5f2c09..082d23f53 100755
--- a/tests/od/od-j.sh
+++ b/tests/od/od-j.sh
@@ -18,6 +18,7 @@
. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
print_ver_ od
+getlimits_
od -j1 /dev/null || fail=1
@@ -38,4 +39,20 @@ for file in ${srcdir=.}/tests/init.sh /proc/version
/sys/kernel/profiling; do
compare experr1 err1 || fail=1
done
+# Test that offset overflows are properly diagnosed.
+OFFSET=$(expr $INTMAX_MAX - 1) || framework_failure_
+
+printf '%o 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n' $OFFSET \
+ >exp-out || framework_failure_
+echo 'od: offset overflow' >exp-err || framework_failure_
+for opt in '' -v; do
+ returns_ 1 timeout 10 od $opt -t x1 -j $OFFSET /dev/zero >out 2>err || fail=1
+ compare exp-out out || fail=1
+ compare exp-err err || fail=1
+done
+
+returns_ 1 timeout 10 od -S 1 -j $OFFSET /dev/zero >out 2>err || fail=1
+compare /dev/null out || fail=1
+compare exp-err err || fail=1
+
Exit $fail
--
2.56.0