Fix some problems with negative and out-of-range integers.

Original problem reported for HP-UX LVM v2.2 by Michael White in
<http://lists.gnu.org/archive/html/bug-tar/2012-10/msg00000.html>.
This patch fixes some other gotchas that I noticed.
* gnulib.modules: Add extern-inline.
* src/common.h: Use _GL_INLINE_HEADER_BEGIN, _GL_INLINE_HEADER_END.
(COMMON_INLINE, max, min): New macros.
(represent_uintmax, valid_timespec): New inline functions.
(SYSINT_BUFSIZE): New constant.
(sysinttostr, strtosysint, decode_timespec): New decls.
* src/create.c (start_private_header): Silently bring the time_t
value into range; it is now the caller's responsibility to deal
with any overflow error.  Use uid 0 and gid 0 rather than the
user's uid/gid, since the faked header isn't "owned" by the user
and the uid/gid could in theory be out of range.  Leave major and
minor zeroed.
(FILL): Remove.
(write_gnu_long_link): Let start_private_header zero things out.
* src/create.c (write_gnu_long_link, write_extended):
* src/xheader.c (xheader_write_global):
Use start_time, not current time; no point hammering on the clock.
* src/compare.c (diff_multivol): Check that offset, size are in range.
* src/incremen.c (read_incr_db_01, write_directory_file_entry):
Allow negative time_t, dev_t, and ino_t.
* src/list.c (max): Remove (moved to common.h).
(read_header): Check that size is in range.
(from_header): Return intmax_t, not uintmax_t, to allow negative.
All callers changed.  At compile time, check assumptions about
intmax_t and uintmax_t.  Use bool for booleans.  Avoid overflow
hassles on picky hosts.
(mode_from_header): Last arg is now bool *, not unsigned *.
All callers changed.
(simple_print_header): Do not assume UID, GID fit in 'long'.
* src/list.c (from_header):
* src/xheader.c (out_of_range_header):
Arg is now a plain minimum value, not minus minval converted to
uintmax_t.  All callers changed.
* src/misc.c (COMMON_INLINE): New macro.
(sysinttostr, strtosysint, decode_timespec): New functions.
* src/sparse.c (oldgnu_add_sparse, oldgnu_fixup_header)
(star_fixup_header):
Check for offset overflow.
(decode_num): Clear errno before calling strtoumax.
* src/tar.c (expand_pax_option): Don't discard nanoseconds.
* src/xheader.c (assign_time_option): Allow negative time_t.
(decode_record): Simplify, since out-of-range string is guaranteed
to produce a value exceeding len_max.
(xheader_read): Last arg is off_t, not size_t.
Caller should diagnose negative arg, as needed.
Check that it's in range.
(enum decode_time_status): Remove.
(_decode_time): Remove, folding into decode_time.
(decode_time): Return bool, not enum decode_time_status.
Rely on decode_timespec to do most of the work.
(code_signed_num): New function.
(code_num): Use it.
(decode_signed_num): New function.
(decode_num): Use it.
(gid_coder, gid_decoder, uid_coder, uid_decoder, sparse_map_decoder)
(sparse_map_decoder): Code and decode negative values.
(sparse_map_decoder): Improve check for out-of-range values.
* tests/time01.at: New file.
* tests/Makefile.am (TESTSUITE_AT): Add it.
* tests/testsuite.at: Include it.
This commit is contained in:
Paul Eggert
2012-12-22 20:41:23 -08:00
parent d8ac237663
commit df7b55a8f6
13 changed files with 472 additions and 315 deletions

View File

@@ -1,7 +1,7 @@
/* Miscellaneous functions, not really specific to GNU tar.
Copyright (C) 1988, 1992, 1994, 1995, 1996, 1997, 1999, 2000, 2001,
2003, 2004, 2005, 2006, 2007, 2009, 2010 Free Software Foundation, Inc.
2003, 2004, 2005, 2006, 2007, 2009, 2010, 2012 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
@@ -17,6 +17,7 @@
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
#define COMMON_INLINE _GL_EXTERN_INLINE
#include <system.h>
#include <rmt.h>
#include "common.h"
@@ -325,6 +326,76 @@ replace_prefix (char **pname, const char *samp, size_t slen,
/* Handling numbers. */
/* Convert VALUE, which is converted from a system integer type whose
minimum value is MINVAL and maximum MINVAL, to an decimal
integer string. Use the storage in BUF and return a pointer to the
converted string. If VALUE is converted from a negative integer in
the range MINVAL .. -1, represent it with a string representation
of the negative integer, using leading '-'. */
#if ! (INTMAX_MAX <= UINTMAX_MAX / 2)
# error "strtosysint accepts uintmax_t to represent intmax_t"
#endif
char *
sysinttostr (uintmax_t value, intmax_t minval, uintmax_t maxval,
char buf[SYSINT_BUFSIZE])
{
if (value <= maxval)
return umaxtostr (value, buf);
else
{
intmax_t i = value - minval;
return imaxtostr (i + minval, buf);
}
}
/* Convert a prefix of the string ARG to a system integer type whose
minimum value is MINVAL and maximum MAXVAL. If MINVAL is negative,
negative integers MINVAL .. -1 are assumed to be represented using
leading '-' in the usual way. If the represented value exceeds
INTMAX_MAX, return a negative integer V such that (uintmax_t) V
yields the represented value. If ARGLIM is nonnull, store into
*ARGLIM a pointer to the first character after the prefix.
This is the inverse of sysinttostr.
On a normal return, set errno = 0.
On conversion error, return 0 and set errno = EINVAL.
On overflow, return an extreme value and set errno = ERANGE. */
#if ! (INTMAX_MAX <= UINTMAX_MAX)
# error "strtosysint accepts uintmax_t to represent nonnegative intmax_t"
#endif
intmax_t
strtosysint (char const *arg, char **arglim, intmax_t minval, uintmax_t maxval)
{
errno = 0;
if (maxval <= INTMAX_MAX)
{
if (ISDIGIT (arg[*arg == '-']))
{
intmax_t i = strtoimax (arg, arglim, 10);
intmax_t imaxval = maxval;
if (minval <= i && i <= imaxval)
return i;
errno = ERANGE;
return i < minval ? minval : maxval;
}
}
else
{
if (ISDIGIT (*arg))
{
uintmax_t i = strtoumax (arg, arglim, 10);
if (i <= maxval)
return represent_uintmax (i);
errno = ERANGE;
return maxval;
}
}
errno = EINVAL;
return 0;
}
/* Output fraction and trailing digits appropriate for a nanoseconds
count equal to NS, but don't output unnecessary '.' or trailing
zeros. */
@@ -381,6 +452,84 @@ code_timespec (struct timespec t, char sbuf[TIMESPEC_STRSIZE_BOUND])
code_ns_fraction (ns, sbuf + UINTMAX_STRSIZE_BOUND);
return np;
}
struct timespec
decode_timespec (char const *arg, char **arg_lim, bool parse_fraction)
{
time_t s = TYPE_MINIMUM (time_t);
int ns = -1;
char const *p = arg;
bool negative = *arg == '-';
struct timespec r;
if (! ISDIGIT (arg[negative]))
errno = EINVAL;
else
{
errno = 0;
if (negative)
{
intmax_t i = strtoimax (arg, arg_lim, 10);
if (TYPE_SIGNED (time_t) ? TYPE_MINIMUM (time_t) <= i : 0 <= i)
s = i;
else
errno = ERANGE;
}
else
{
uintmax_t i = strtoumax (arg, arg_lim, 10);
if (i <= TYPE_MAXIMUM (time_t))
s = i;
else
errno = ERANGE;
}
p = *arg_lim;
ns = 0;
if (parse_fraction && *p == '.')
{
int digits = 0;
bool trailing_nonzero = false;
while (ISDIGIT (*++p))
if (digits < LOG10_BILLION)
digits++, ns = 10 * ns + (*p - '0');
else
trailing_nonzero |= *p != '0';
while (digits < LOG10_BILLION)
digits++, ns *= 10;
if (negative)
{
/* Convert "-1.10000000000001" to s == -2, ns == 89999999.
I.e., truncate time stamps towards minus infinity while
converting them to internal form. */
ns += trailing_nonzero;
if (ns != 0)
{
if (s == TYPE_MINIMUM (time_t))
ns = -1;
else
{
s--;
ns = BILLION - ns;
}
}
}
}
if (errno == ERANGE)
ns = -1;
}
*arg_lim = (char *) p;
r.tv_sec = s;
r.tv_nsec = ns;
return r;
}
/* File handling. */