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


Reply via email to