coreutils/backport-dd-improve-integer-overflow-checking.patch
2022-03-29 18:50:31 +08:00

834 lines
30 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From e94d95075dd919e5e6ec0c8ed09477e58b863788 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Tue, 18 Jan 2022 13:22:02 -0800
Subject: [PATCH] dd: improve integer overflow checking
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* src/dd.c: Prefer signed to unsigned types where either will do,
as this helps improve checking with gcc -fsanitize=undefined.
Limit the signed types to their intended ranges.
(MAX_BLOCKSIZE): Dont exceed IDX_MAX - slop either.
(input_offset_overflow): Remove; overflow now denoted by negative.
(parse_integer): Return INTMAX_MAX on overflow, instead of unspecified.
Do not falsely report overflow for 00x99999999999999999999999999999.
* tests/dd/misc.sh: New test for 00xBIG.
* tests/dd/skip-seek-past-file.sh: Adjust to new diagnostic wording.
New test for BIGxBIG.
---
src/dd.c | 298 +++++++++++++++++---------------
tests/dd/misc.sh | 9 +-
tests/dd/skip-seek-past-file.sh | 9 +-
3 files changed, 177 insertions(+), 139 deletions(-)
diff --git a/src/dd.c b/src/dd.c
index 35002f4d2e..bde92e97a4 100644
--- a/src/dd.c
+++ b/src/dd.c
@@ -98,11 +98,12 @@
#define OUTPUT_BLOCK_SLOP (page_size - 1)
/* Maximum blocksize for the given SLOP.
- Keep it smaller than SIZE_MAX - SLOP, so that we can
+ Keep it smaller than MIN (IDX_MAX, SIZE_MAX) - SLOP, so that we can
allocate buffers that size. Keep it smaller than SSIZE_MAX, for
the benefit of system calls like "read". And keep it smaller than
OFF_T_MAX, for the benefit of the large-offset seek code. */
-#define MAX_BLOCKSIZE(slop) MIN (SIZE_MAX - (slop), MIN (SSIZE_MAX, OFF_T_MAX))
+#define MAX_BLOCKSIZE(slop) MIN (MIN (IDX_MAX, SIZE_MAX) - (slop), \
+ MIN (SSIZE_MAX, OFF_T_MAX))
/* Conversions bit masks. */
enum
@@ -148,39 +149,39 @@ static char const *input_file = NULL;
static char const *output_file = NULL;
/* The page size on this host. */
-static size_t page_size;
+static idx_t page_size;
/* The number of bytes in which atomic reads are done. */
-static size_t input_blocksize = 0;
+static idx_t input_blocksize = 0;
/* The number of bytes in which atomic writes are done. */
-static size_t output_blocksize = 0;
+static idx_t output_blocksize = 0;
/* Conversion buffer size, in bytes. 0 prevents conversions. */
-static size_t conversion_blocksize = 0;
+static idx_t conversion_blocksize = 0;
/* Skip this many records of 'input_blocksize' bytes before input. */
-static uintmax_t skip_records = 0;
+static intmax_t skip_records = 0;
/* Skip this many bytes before input in addition of 'skip_records'
records. */
-static size_t skip_bytes = 0;
+static idx_t skip_bytes = 0;
/* Skip this many records of 'output_blocksize' bytes before output. */
-static uintmax_t seek_records = 0;
+static intmax_t seek_records = 0;
/* Skip this many bytes in addition to 'seek_records' records before
output. */
-static uintmax_t seek_bytes = 0;
+static intmax_t seek_bytes = 0;
/* Whether the final output was done with a seek (rather than a write). */
static bool final_op_was_seek;
/* Copy only this many records. The default is effectively infinity. */
-static uintmax_t max_records = (uintmax_t) -1;
+static intmax_t max_records = INTMAX_MAX;
/* Copy this many bytes in addition to 'max_records' records. */
-static size_t max_bytes = 0;
+static idx_t max_bytes = 0;
/* Bit vector of conversions to apply. */
static int conversions_mask = 0;
@@ -196,19 +197,19 @@ static int status_level = STATUS_DEFAULT;
static bool translation_needed = false;
/* Number of partial blocks written. */
-static uintmax_t w_partial = 0;
+static intmax_t w_partial = 0;
/* Number of full blocks written. */
-static uintmax_t w_full = 0;
+static intmax_t w_full = 0;
/* Number of partial blocks read. */
-static uintmax_t r_partial = 0;
+static intmax_t r_partial = 0;
/* Number of full blocks read. */
-static uintmax_t r_full = 0;
+static intmax_t r_full = 0;
/* Number of bytes written. */
-static uintmax_t w_bytes = 0;
+static intmax_t w_bytes = 0;
/* Time that dd started. */
static xtime_t start_time;
@@ -226,16 +227,14 @@ static bool input_seekable;
If ESPIPE, do not issue any more diagnostics about it. */
static int input_seek_errno;
-/* File offset of the input, in bytes, along with a flag recording
- whether it overflowed. */
-static uintmax_t input_offset;
-static bool input_offset_overflow;
+/* File offset of the input, in bytes, or -1 if it overflowed. */
+static off_t input_offset;
/* True if a partial read should be diagnosed. */
static bool warn_partial_read;
/* Records truncated by conv=block. */
-static uintmax_t r_truncate = 0;
+static intmax_t r_truncate = 0;
/* Output representation of newline and space characters.
They change if we're converting to EBCDIC. */
@@ -253,10 +252,10 @@ static char *ibuf;
static char *obuf;
/* Current index into 'obuf'. */
-static size_t oc = 0;
+static idx_t oc = 0;
/* Index into current line, for 'conv=block' and 'conv=unblock'. */
-static size_t col = 0;
+static idx_t col = 0;
/* The set of signals that are caught. */
static sigset_t caught_signals;
@@ -274,7 +273,7 @@ static bool i_nocache, o_nocache;
static bool i_nocache_eof, o_nocache_eof;
/* Function used for read (to handle iflag=fullblock parameter). */
-static ssize_t (*iread_fnc) (int fd, char *buf, size_t size);
+static ssize_t (*iread_fnc) (int fd, char *buf, idx_t size);
/* A longest symbol in the struct symbol_values tables below. */
#define LONGEST_SYMBOL "count_bytes"
@@ -701,11 +700,10 @@ alloc_ibuf (void)
char *buf = malloc (input_blocksize + INPUT_BLOCK_SLOP);
if (!buf)
{
- uintmax_t ibs = input_blocksize;
char hbuf[LONGEST_HUMAN_READABLE + 1];
die (EXIT_FAILURE, 0,
- _("memory exhausted by input buffer of size %"PRIuMAX" bytes (%s)"),
- ibs,
+ _("memory exhausted by input buffer of size %td bytes (%s)"),
+ input_blocksize,
human_readable (input_blocksize, hbuf,
human_opts | human_base_1024, 1, 1));
}
@@ -729,12 +727,11 @@ alloc_obuf (void)
char *buf = malloc (output_blocksize + OUTPUT_BLOCK_SLOP);
if (!buf)
{
- uintmax_t obs = output_blocksize;
char hbuf[LONGEST_HUMAN_READABLE + 1];
die (EXIT_FAILURE, 0,
- _("memory exhausted by output buffer of size %"PRIuMAX
+ _("memory exhausted by output buffer of size %td"
" bytes (%s)"),
- obs,
+ output_blocksize,
human_readable (output_blocksize, hbuf,
human_opts | human_base_1024, 1, 1));
}
@@ -793,8 +790,7 @@ print_xfer_stats (xtime_t progress_time)
if (start_time < now)
{
double XTIME_PRECISIONe0 = XTIME_PRECISION;
- uintmax_t delta_xtime = now;
- delta_xtime -= start_time;
+ xtime_t delta_xtime = now - start_time;
delta_s = delta_xtime / XTIME_PRECISIONe0;
bytes_per_second = human_readable (w_bytes, bpsbuf, human_opts,
XTIME_PRECISION, delta_xtime);
@@ -822,16 +818,16 @@ print_xfer_stats (xtime_t progress_time)
int stats_len
= (abbreviation_lacks_prefix (si)
? fprintf (stderr,
- ngettext ("%"PRIuMAX" byte copied, %s, %s",
- "%"PRIuMAX" bytes copied, %s, %s",
+ ngettext ("%"PRIdMAX" byte copied, %s, %s",
+ "%"PRIdMAX" bytes copied, %s, %s",
select_plural (w_bytes)),
w_bytes, delta_s_buf, bytes_per_second)
: abbreviation_lacks_prefix (iec)
? fprintf (stderr,
- _("%"PRIuMAX" bytes (%s) copied, %s, %s"),
+ _("%"PRIdMAX" bytes (%s) copied, %s, %s"),
w_bytes, si, delta_s_buf, bytes_per_second)
: fprintf (stderr,
- _("%"PRIuMAX" bytes (%s, %s) copied, %s, %s"),
+ _("%"PRIdMAX" bytes (%s, %s) copied, %s, %s"),
w_bytes, si, iec, delta_s_buf, bytes_per_second));
if (progress_time)
@@ -863,14 +859,14 @@ print_stats (void)
}
fprintf (stderr,
- _("%"PRIuMAX"+%"PRIuMAX" records in\n"
- "%"PRIuMAX"+%"PRIuMAX" records out\n"),
+ _("%"PRIdMAX"+%"PRIdMAX" records in\n"
+ "%"PRIdMAX"+%"PRIdMAX" records out\n"),
r_full, r_partial, w_full, w_partial);
if (r_truncate != 0)
fprintf (stderr,
- ngettext ("%"PRIuMAX" truncated record\n",
- "%"PRIuMAX" truncated records\n",
+ ngettext ("%"PRIdMAX" truncated record\n",
+ "%"PRIdMAX" truncated records\n",
select_plural (r_truncate)),
r_truncate);
@@ -1050,7 +1046,9 @@ cache_round (int fd, off_t len)
if (len)
{
- uintmax_t c_pending = *pending + len;
+ intmax_t c_pending;
+ if (INT_ADD_WRAPV (*pending, len, &c_pending))
+ c_pending = INTMAX_MAX;
*pending = c_pending % IO_BUFSIZE;
if (c_pending > *pending)
len = c_pending - *pending;
@@ -1138,7 +1136,7 @@ invalidate_cache (int fd, off_t len)
bytes read if successful, -1 (setting errno) on failure. */
static ssize_t
-iread (int fd, char *buf, size_t size)
+iread (int fd, char *buf, idx_t size)
{
ssize_t nread;
static ssize_t prev_nread;
@@ -1167,11 +1165,11 @@ iread (int fd, char *buf, size_t size)
{
if (0 < prev_nread && prev_nread < size)
{
- uintmax_t prev = prev_nread;
+ idx_t prev = prev_nread;
if (status_level != STATUS_NONE)
- error (0, 0, ngettext (("warning: partial read (%"PRIuMAX" byte); "
+ error (0, 0, ngettext (("warning: partial read (%td byte); "
"suggest iflag=fullblock"),
- ("warning: partial read (%"PRIuMAX" bytes); "
+ ("warning: partial read (%td bytes); "
"suggest iflag=fullblock"),
select_plural (prev)),
prev);
@@ -1185,7 +1183,7 @@ iread (int fd, char *buf, size_t size)
/* Wrapper around iread function to accumulate full blocks. */
static ssize_t
-iread_fullblock (int fd, char *buf, size_t size)
+iread_fullblock (int fd, char *buf, idx_t size)
{
ssize_t nread = 0;
@@ -1209,10 +1207,10 @@ iread_fullblock (int fd, char *buf, size_t size)
this is less than SIZE. Keep trying if there are partial
writes. */
-static size_t
-iwrite (int fd, char const *buf, size_t size)
+static idx_t
+iwrite (int fd, char const *buf, idx_t size)
{
- size_t total_written = 0;
+ idx_t total_written = 0;
if ((output_flags & O_DIRECT) && size < output_blocksize)
{
@@ -1290,7 +1288,7 @@ iwrite (int fd, char const *buf, size_t size)
static void
write_output (void)
{
- size_t nwritten = iwrite (STDOUT_FILENO, obuf, output_blocksize);
+ idx_t nwritten = iwrite (STDOUT_FILENO, obuf, output_blocksize);
w_bytes += nwritten;
if (nwritten != output_blocksize)
{
@@ -1422,7 +1420,7 @@ parse_symbols (char const *str, struct symbol_value const *table,
{
if (! entry->symbol[0])
{
- size_t slen = strcomma ? strcomma - str : strlen (str);
+ idx_t slen = strcomma ? strcomma - str : strlen (str);
error (0, 0, "%s: %s", _(error_msgid),
quotearg_n_style_mem (0, locale_quoting_style, str, slen));
usage (EXIT_FAILURE);
@@ -1443,40 +1441,61 @@ parse_symbols (char const *str, struct symbol_value const *table,
/* Return the value of STR, interpreted as a non-negative decimal integer,
optionally multiplied by various values.
- Set *INVALID to a nonzero error value if STR does not represent a
- number in this format. */
+ If STR does not represent a number in this format,
+ set *INVALID to a nonzero error value and return
+ INTMAX_MAX if it is an overflow, an indeterminate value otherwise. */
-static uintmax_t
+static intmax_t
parse_integer (char const *str, strtol_error *invalid)
{
+ /* Call xstrtoumax, not xstrtoimax, since we don't want to
+ allow strings like " -0". */
uintmax_t n;
char *suffix;
strtol_error e = xstrtoumax (str, &suffix, 10, &n, "bcEGkKMPTwYZ0");
- if (e == LONGINT_INVALID_SUFFIX_CHAR && *suffix == 'x')
+ if ((e & ~LONGINT_OVERFLOW) == LONGINT_INVALID_SUFFIX_CHAR
+ && *suffix == 'x')
{
- uintmax_t multiplier = parse_integer (suffix + 1, invalid);
+ strtol_error invalid2 = LONGINT_OK;
+ intmax_t result = parse_integer (suffix + 1, &invalid2);
+ if ((invalid2 & ~LONGINT_OVERFLOW) != LONGINT_OK)
+ {
+ *invalid = invalid2;
+ return result;
+ }
- if (multiplier != 0 && n * multiplier / multiplier != n)
+ if (INT_MULTIPLY_WRAPV (n, result, &result))
{
*invalid = LONGINT_OVERFLOW;
- return 0;
+ return INTMAX_MAX;
}
- if (n == 0 && STRPREFIX (str, "0x"))
- error (0, 0,
- _("warning: %s is a zero multiplier; "
- "use %s if that is intended"),
- quote_n (0, "0x"), quote_n (1, "00x"));
+ if (result == 0)
+ {
+ if (STRPREFIX (str, "0x"))
+ error (0, 0,
+ _("warning: %s is a zero multiplier; "
+ "use %s if that is intended"),
+ quote_n (0, "0x"), quote_n (1, "00x"));
+ }
+ else if ((e | invalid2) & LONGINT_OVERFLOW)
+ {
+ *invalid = LONGINT_OVERFLOW;
+ return INTMAX_MAX;
+ }
- n *= multiplier;
+ return result;
}
- else if (e != LONGINT_OK)
+
+ if (INTMAX_MAX < n)
{
- *invalid = e;
- return 0;
+ *invalid = LONGINT_OVERFLOW;
+ return INTMAX_MAX;
}
+ if (e != LONGINT_OK)
+ *invalid = e;
return n;
}
@@ -1492,10 +1511,10 @@ operand_is (char const *operand, char const *name)
static void
scanargs (int argc, char *const *argv)
{
- size_t blocksize = 0;
- uintmax_t count = (uintmax_t) -1;
- uintmax_t skip = 0;
- uintmax_t seek = 0;
+ idx_t blocksize = 0;
+ intmax_t count = INTMAX_MAX;
+ intmax_t skip = 0;
+ intmax_t seek = 0;
for (int i = optind; i < argc; i++)
{
@@ -1529,33 +1548,34 @@ scanargs (int argc, char *const *argv)
else
{
strtol_error invalid = LONGINT_OK;
- uintmax_t n = parse_integer (val, &invalid);
- uintmax_t n_min = 0;
- uintmax_t n_max = UINTMAX_MAX;
+ intmax_t n = parse_integer (val, &invalid);
+ intmax_t n_min = 0;
+ intmax_t n_max = INTMAX_MAX;
+ idx_t *converted_idx = NULL;
if (operand_is (name, "ibs"))
{
n_min = 1;
n_max = MAX_BLOCKSIZE (INPUT_BLOCK_SLOP);
- input_blocksize = n;
+ converted_idx = &input_blocksize;
}
else if (operand_is (name, "obs"))
{
n_min = 1;
n_max = MAX_BLOCKSIZE (OUTPUT_BLOCK_SLOP);
- output_blocksize = n;
+ converted_idx = &output_blocksize;
}
else if (operand_is (name, "bs"))
{
n_min = 1;
n_max = MAX_BLOCKSIZE (INPUT_BLOCK_SLOP);
- blocksize = n;
+ converted_idx = &blocksize;
}
else if (operand_is (name, "cbs"))
{
n_min = 1;
- n_max = SIZE_MAX;
- conversion_blocksize = n;
+ n_max = MIN (SIZE_MAX, IDX_MAX);
+ converted_idx = &conversion_blocksize;
}
else if (operand_is (name, "skip"))
skip = n;
@@ -1578,6 +1598,8 @@ scanargs (int argc, char *const *argv)
if (invalid != LONGINT_OK)
die (EXIT_FAILURE, invalid == LONGINT_OVERFLOW ? EOVERFLOW : 0,
"%s: %s", _("invalid number"), quote (val));
+ else if (converted_idx)
+ *converted_idx = n;
}
}
@@ -1628,12 +1650,12 @@ scanargs (int argc, char *const *argv)
else if (skip != 0)
skip_records = skip;
- if (input_flags & O_COUNT_BYTES && count != (uintmax_t) -1)
+ if (input_flags & O_COUNT_BYTES && count != INTMAX_MAX)
{
max_records = count / input_blocksize;
max_bytes = count % input_blocksize;
}
- else if (count != (uintmax_t) -1)
+ else if (count != INTMAX_MAX)
max_records = count;
if (output_flags & O_SEEK_BYTES && seek != 0)
@@ -1651,7 +1673,7 @@ scanargs (int argc, char *const *argv)
warn_partial_read =
(! (conversions_mask & C_TWOBUFS) && ! (input_flags & O_FULLBLOCK)
&& (skip_records
- || (0 < max_records && max_records < (uintmax_t) -1)
+ || (0 < max_records && max_records < INTMAX_MAX)
|| (input_flags | output_flags) & O_DIRECT));
iread_fnc = ((input_flags & O_FULLBLOCK)
@@ -1726,9 +1748,9 @@ apply_translations (void)
to the NREAD bytes in BUF. */
static void
-translate_buffer (char *buf, size_t nread)
+translate_buffer (char *buf, idx_t nread)
{
- size_t i;
+ idx_t i;
char *cp;
for (i = nread, cp = buf; i; i--, cp++)
*cp = trans_table[to_uchar (*cp)];
@@ -1746,7 +1768,7 @@ static char saved_char;
next call. Return the new start of the BUF buffer. */
static char *
-swab_buffer (char *buf, size_t *nread)
+swab_buffer (char *buf, idx_t *nread)
{
char *bufstart = buf;
@@ -1770,7 +1792,7 @@ swab_buffer (char *buf, size_t *nread)
toward the beginning. This way we only move half of the data. */
char *cp = bufstart + *nread; /* Start one char past the last. */
- for (size_t i = *nread / 2; i; i--, cp -= 2)
+ for (idx_t i = *nread >> 1; i; i--, cp -= 2)
*cp = *(cp - 2);
return ++bufstart;
@@ -1780,11 +1802,10 @@ swab_buffer (char *buf, size_t *nread)
necessary. */
static void
-advance_input_offset (uintmax_t offset)
+advance_input_offset (intmax_t offset)
{
- input_offset += offset;
- if (input_offset < offset)
- input_offset_overflow = true;
+ if (0 <= input_offset && INT_ADD_WRAPV (input_offset, offset, &input_offset))
+ input_offset = -1;
}
/* Throw away RECORDS blocks of BLOCKSIZE bytes plus BYTES bytes on
@@ -1796,18 +1817,18 @@ advance_input_offset (uintmax_t offset)
reached. If FDESC is STDOUT_FILENO, on return, BYTES is the
remaining bytes in addition to the remaining records. */
-static uintmax_t
-skip (int fdesc, char const *file, uintmax_t records, size_t blocksize,
- size_t *bytes)
+static intmax_t
+skip (int fdesc, char const *file, intmax_t records, idx_t blocksize,
+ idx_t *bytes)
{
- uintmax_t offset = records * blocksize + *bytes;
-
/* Try lseek and if an error indicates it was an inappropriate operation --
or if the file offset is not representable as an off_t --
fall back on using read. */
errno = 0;
- if (records <= OFF_T_MAX / blocksize
+ off_t offset;
+ if (! INT_MULTIPLY_WRAPV (records, blocksize, &offset)
+ && ! INT_ADD_WRAPV (offset, *bytes, &offset)
&& 0 <= lseek (fdesc, offset, SEEK_CUR))
{
if (fdesc == STDIN_FILENO)
@@ -1815,7 +1836,8 @@ skip (int fdesc, char const *file, uintmax_t records, size_t blocksize,
struct stat st;
if (ifstat (STDIN_FILENO, &st) != 0)
die (EXIT_FAILURE, errno, _("cannot fstat %s"), quoteaf (file));
- if (usable_st_size (&st) && st.st_size < input_offset + offset)
+ if (usable_st_size (&st) && 0 <= input_offset
+ && st.st_size - input_offset < offset)
{
/* When skipping past EOF, return the number of _full_ blocks
* that are not skipped, and set offset to EOF, so the caller
@@ -1920,7 +1942,7 @@ skip (int fdesc, char const *file, uintmax_t records, size_t blocksize,
be seekable. */
static bool
-advance_input_after_read_error (size_t nbytes)
+advance_input_after_read_error (idx_t nbytes)
{
if (! input_seekable)
{
@@ -1932,8 +1954,7 @@ advance_input_after_read_error (size_t nbytes)
{
off_t offset;
advance_input_offset (nbytes);
- input_offset_overflow |= (OFF_T_MAX < input_offset);
- if (input_offset_overflow)
+ if (input_offset < 0)
{
error (0, 0, _("offset overflow while reading file %s"),
quoteaf (input_file));
@@ -1962,13 +1983,13 @@ advance_input_after_read_error (size_t nbytes)
/* Copy NREAD bytes of BUF, with no conversions. */
static void
-copy_simple (char const *buf, size_t nread)
+copy_simple (char const *buf, idx_t nread)
{
char const *start = buf; /* First uncopied char in BUF. */
do
{
- size_t nfree = MIN (nread, output_blocksize - oc);
+ idx_t nfree = MIN (nread, output_blocksize - oc);
memcpy (obuf + oc, start, nfree);
@@ -1986,15 +2007,15 @@ copy_simple (char const *buf, size_t nread)
replacing the newline with trailing spaces). */
static void
-copy_with_block (char const *buf, size_t nread)
+copy_with_block (char const *buf, idx_t nread)
{
- for (size_t i = nread; i; i--, buf++)
+ for (idx_t i = nread; i; i--, buf++)
{
if (*buf == newline_character)
{
if (col < conversion_blocksize)
{
- size_t j;
+ idx_t j;
for (j = col; j < conversion_blocksize; j++)
output_char (space_character);
}
@@ -2016,11 +2037,11 @@ copy_with_block (char const *buf, size_t nread)
with a newline). */
static void
-copy_with_unblock (char const *buf, size_t nread)
+copy_with_unblock (char const *buf, idx_t nread)
{
- static size_t pending_spaces = 0;
+ static idx_t pending_spaces = 0;
- for (size_t i = 0; i < nread; i++)
+ for (idx_t i = 0; i < nread; i++)
{
char c = buf[i];
@@ -2104,10 +2125,10 @@ dd_copy (void)
/* If nonzero, then the previously read block was partial and
PARTREAD was its size. */
- size_t partread = 0;
+ idx_t partread = 0;
int exit_status = EXIT_SUCCESS;
- size_t n_bytes_read;
+ idx_t n_bytes_read;
/* Leave at least one extra byte at the beginning and end of 'ibuf'
for conv=swab, but keep the buffer address even. But some peculiar
@@ -2128,11 +2149,13 @@ dd_copy (void)
if (skip_records != 0 || skip_bytes != 0)
{
- uintmax_t us_bytes = input_offset + (skip_records * input_blocksize)
- + skip_bytes;
- uintmax_t us_blocks = skip (STDIN_FILENO, input_file,
- skip_records, input_blocksize, &skip_bytes);
- us_bytes -= input_offset;
+ intmax_t us_bytes;
+ bool us_bytes_overflow =
+ (INT_MULTIPLY_WRAPV (skip_records, input_blocksize, &us_bytes)
+ || INT_ADD_WRAPV (skip_bytes, us_bytes, &us_bytes));
+ off_t input_offset0 = input_offset;
+ intmax_t us_blocks = skip (STDIN_FILENO, input_file,
+ skip_records, input_blocksize, &skip_bytes);
/* POSIX doesn't say what to do when dd detects it has been
asked to skip past EOF, so I assume it's non-fatal.
@@ -2140,7 +2163,10 @@ dd_copy (void)
1. file is too small
2. pipe has not enough data
3. partial reads */
- if ((us_blocks || (!input_offset_overflow && us_bytes))
+ if ((us_blocks
+ || (0 <= input_offset
+ && (us_bytes_overflow
+ || us_bytes != input_offset - input_offset0)))
&& status_level != STATUS_NONE)
{
error (0, 0,
@@ -2150,8 +2176,8 @@ dd_copy (void)
if (seek_records != 0 || seek_bytes != 0)
{
- size_t bytes = seek_bytes;
- uintmax_t write_records = skip (STDOUT_FILENO, output_file,
+ idx_t bytes = seek_bytes;
+ intmax_t write_records = skip (STDOUT_FILENO, output_file,
seek_records, output_blocksize, &bytes);
if (write_records != 0 || bytes != 0)
@@ -2160,7 +2186,7 @@ dd_copy (void)
do
{
- size_t size = write_records ? output_blocksize : bytes;
+ idx_t size = write_records ? output_blocksize : bytes;
if (iwrite (STDOUT_FILENO, obuf, size) != size)
{
error (0, errno, _("writing to %s"), quoteaf (output_file));
@@ -2230,7 +2256,7 @@ dd_copy (void)
if (conversions_mask & C_NOERROR)
{
print_stats ();
- size_t bad_portion = input_blocksize - partread;
+ idx_t bad_portion = input_blocksize - partread;
/* We already know this data is not cached,
but call this so that correct offsets are maintained. */
@@ -2284,7 +2310,7 @@ dd_copy (void)
if (ibuf == obuf) /* If not C_TWOBUFS. */
{
- size_t nwritten = iwrite (STDOUT_FILENO, obuf, n_bytes_read);
+ idx_t nwritten = iwrite (STDOUT_FILENO, obuf, n_bytes_read);
w_bytes += nwritten;
if (nwritten != n_bytes_read)
{
@@ -2331,7 +2357,7 @@ dd_copy (void)
{
/* If the final input line didn't end with a '\n', pad
the output block to 'conversion_blocksize' chars. */
- for (size_t i = col; i < conversion_blocksize; i++)
+ for (idx_t i = col; i < conversion_blocksize; i++)
output_char (space_character);
}
@@ -2344,7 +2370,7 @@ dd_copy (void)
/* Write out the last block. */
if (oc != 0)
{
- size_t nwritten = iwrite (STDOUT_FILENO, obuf, oc);
+ idx_t nwritten = iwrite (STDOUT_FILENO, obuf, oc);
w_bytes += nwritten;
if (nwritten != 0)
w_partial++;
@@ -2477,15 +2503,14 @@ main (int argc, char **argv)
if (seek_records != 0 && !(conversions_mask & C_NOTRUNC))
{
- uintmax_t size = seek_records * output_blocksize + seek_bytes;
- unsigned long int obs = output_blocksize;
-
- if (OFF_T_MAX / output_blocksize < seek_records)
+ off_t size;
+ if (INT_MULTIPLY_WRAPV (seek_records, output_blocksize, &size)
+ || INT_ADD_WRAPV (seek_bytes, size, &size))
die (EXIT_FAILURE, 0,
_("offset too large: "
- "cannot truncate to a length of seek=%"PRIuMAX""
- " (%lu-byte) blocks"),
- seek_records, obs);
+ "cannot truncate to a length of seek=%"PRIdMAX""
+ " (%td-byte) blocks"),
+ seek_records, output_blocksize);
if (iftruncate (STDOUT_FILENO, size) != 0)
{
@@ -2502,10 +2527,13 @@ main (int argc, char **argv)
if (S_ISREG (stdout_stat.st_mode)
|| S_ISDIR (stdout_stat.st_mode)
|| S_TYPEISSHM (&stdout_stat))
- die (EXIT_FAILURE, ftruncate_errno,
- _("failed to truncate to %"PRIuMAX" bytes"
- " in output file %s"),
- size, quoteaf (output_file));
+ {
+ intmax_t isize = size;
+ die (EXIT_FAILURE, ftruncate_errno,
+ _("failed to truncate to %"PRIdMAX" bytes"
+ " in output file %s"),
+ isize, quoteaf (output_file));
+ }
}
}
}
diff --git a/tests/dd/misc.sh b/tests/dd/misc.sh
index 6ca54faac3..d20cbacc87 100755
--- a/tests/dd/misc.sh
+++ b/tests/dd/misc.sh
@@ -19,6 +19,7 @@
. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
print_ver_ dd
+export LC_ALL=C
tmp_in=dd-in
tmp_in2=dd-in2
@@ -98,7 +99,7 @@ test "$outbytes" -eq 3 || fail=1
# A delay is required to trigger a failure.
# There might be some missed failures but it's unlikely.
(echo a; sleep .1; echo b) \
- | env LC_ALL=C dd bs=4 status=noxfer iflag=fullblock >out 2>err || fail=1
+ | dd bs=4 status=noxfer iflag=fullblock >out 2>err || fail=1
printf 'a\nb\n' > out_ok || framework_failure_
echo "1+0 records in
1+0 records out" > err_ok || framework_failure_
@@ -116,5 +117,11 @@ dd: warning: '0x' is a zero multiplier; use '00x' if that is intended
EOF
compare exp err || fail=1
+echo "0+0 records in
+0+0 records out" >err_ok || framework_failure_
+big=9999999999999999999999999999999999999999999999999999999999999
+dd if=$tmp_in of=$tmp_out count=00x$big status=noxfer 2>err || fail=1
+compare /dev/null $tmp_out || fail=1
+compare err_ok err || fail=1
Exit $fail
diff --git a/tests/dd/skip-seek-past-file.sh b/tests/dd/skip-seek-past-file.sh
index 7c2baa2e1a..e952448e2b 100755
--- a/tests/dd/skip-seek-past-file.sh
+++ b/tests/dd/skip-seek-past-file.sh
@@ -20,7 +20,7 @@
print_ver_ dd
require_sparse_support_ # for 'truncate --size=$OFF_T_MAX'
eval $(getlimits) # for OFF_T limits
-
+export LC_ALL=C
printf "1234" > file || framework_failure_
@@ -65,8 +65,11 @@ compare err_ok err || fail=1
# skipping > OFF_T_MAX should fail immediately
dd bs=1 skip=$OFF_T_OFLOW count=0 status=noxfer < file 2> err && fail=1
-# error message should be "... cannot skip: strerror(EOVERFLOW)"
-grep "cannot skip:" err >/dev/null || fail=1
+# error message should be "... invalid number: strerror(EOVERFLOW)"
+grep "invalid number:" err >/dev/null || fail=1
+dd bs=1 skip=${OFF_T_OFLOW}x$OFF_T_OFLOW count=0 status=noxfer < file 2> err &&
+ fail=1
+grep "invalid number:" err >/dev/null || fail=1
# skipping > max file size should fail immediately
if ! truncate --size=$OFF_T_MAX in 2>/dev/null; then