bug-tar
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Bug-tar] tar-1.15.1+: numerous bugs in xheader.c


From: Jim Meyering
Subject: [Bug-tar] tar-1.15.1+: numerous bugs in xheader.c
Date: Fri, 17 Jun 2005 20:33:46 +0200

[ This is all regarding the latest code in cvs. ]

This expedition started when I found an unchecked call to calloc
in tar's xheader.c.  Here's what can result:

  # Create a sparse file
  $ dd bs=1 seek=1M of=big < /dev/null 2> /dev/null

  # Put it in a tar archive, using the sparse option and POSIX/pax format
  $ ./tar --sparse --format=posix -cf x.tar big

  # Corrupt the tar archive so that the unchecked calloc fails.
  $ perl -pi -e 's/26 (GNU.sparse.numblocks)=1/35 $1=4294967296/' x.tar
  # Remove NUL bytes to compensate for the 9 digits added above.
  $ perl -0777 -pi -e 's/(ctime=[^\n]+\n)\0{9}/$1/m' x.tar

Then, any attempt to list the archive or to extract anything from it
results in an attempt to dereference the NULL pointer:

  $ ./tar tf x.tar
  [Exit 139 (SEGV)]

Or, change the 2^32 to e.g., 2_32+1 to make tar write beyond
the end of an allocated buffer.

With the patch below, you'd get this instead:

  $ ./tar tf x.tar
  ./tar: Malformed header: invalid number of sparse blocks: 4294967296
  ./tar: Error is not recoverable: exiting now
  [Exit 2]

But then I noticed that since I'd chosen 2^32 as the number of blocks,
simply changing calloc to xcalloc didn't solve the problem.
The next problem is here:

  static void
  sparse_numblocks_decoder (struct tar_stat_info *st, char const *arg)
  {
    uintmax_t u;
    if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK)
      {
        st->sparse_map_size = u;
        st->sparse_map = calloc(st->sparse_map_size, sizeof(st->sparse_map[0]));
        st->sparse_map_avail = 0;
      }
  }

This function assigns `u's value (of type uintmax_t) to
`sparse_map_size', which is of type size_t, so with u=2^32,
st->sparse_map_size ends up being zero on a system with a 32-bit size_t.
Another segfault.

Not only that, but if the string, `arg' is invalid (e.g., non-numeric),
this function simply returns, and that results in a similar failure,
because sparse_offset_decoder stores into st->sparse_map[0] even
though st->sparse_map is NULL
and    st->sparse_map_size is 0.

Some of the other *_decoder functions have similar problems.
IMHO, any header decoder/parsing failure should cause tar to fail.

I spotted another problem: sparse_numbytes_decoder can overflow
st->sparse_map_size (that happens when a contrived tar file contains
too many GNU.sparse.numbytes=N lines) and end up corrupting the heap.
I suggest rewriting it to use x2nrealloc (from gnulib's xmalloc.c)
rather than xrealloc.

In testing, I found that my change induced many failures,
each because of the new ``Malformed header: invalid time: ...' error.
That was because decode_time's first xstrtoumax call was always
failing for time strings like this: 1119018481.000000000.
I.e., the existing code totally ignored time strings like
that one, since the `.' was treated as an invalid suffix.

[I'm sure you know already, but I have to say it... ]
All of this suggests there is a pressing need for additions to
the test suite.

Here's a patch that fixes all of the above problems:
[ It'd be nice to have a testing aid with which to create tar
  archives made with selected (invalid) values for given fields. ]

2005-06-17  Jim Meyering  <address@hidden>

        Carefully crafted invalid headers can cause buffer overrun.
        Invalid header fields go undiagnosed.
        Some valid time strings are ignored.

        * src/xheader.c (sparse_numblocks_decoder): Remove unchecked use
        of `calloc'.  Use xcalloc instead.
        (decode_time, gid_decoder, size_decoder, uid_decoder):
        (sparse_size_decoder, sparse_offset_decoder, sparse_numblocks_decoder):
        Ensure that the result of calling xstrtoumax is no larger than
        the maximum value for the target type.  Upon any failure, exit with
        a diagnostic.
        (sparse_numblocks_decoder): Avoid buffer overrun/heap corruption:
        use x2nrealloc, rather than `n *= 2' and xrealloc(p, n,....
        (decode_time): Rewrite to accept time strings like 1119018481.000000000.
        Before, such strings were always ignored.

Index: src/xheader.c
===================================================================
RCS file: /cvsroot/tar/tar/src/xheader.c,v
retrieving revision 1.26
diff -u -p -r1.26 xheader.c
--- src/xheader.c       21 May 2005 23:10:42 -0000      1.26
+++ src/xheader.c       17 Jun 2005 16:08:44 -0000
@@ -246,7 +246,7 @@ xheader_format_name (struct tar_stat_inf
        case 'd':
          if (st)
            {
-             dir = safer_name_suffix (dir_name (st->orig_file_name), 
+             dir = safer_name_suffix (dir_name (st->orig_file_name),
                                       false, absolute_names_option);
              len += strlen (dir) - 1;
            }
@@ -777,12 +777,25 @@ decode_time (char const *arg, time_t *se
 {
   uintmax_t u;
   char *p;
-  if (xstrtoumax (arg, &p, 10, &u, "") == LONGINT_OK)
+  if (xstrtoumax (arg, &p, 10, &u, NULL) == LONGINT_OK
+      && u <= TYPE_MAXIMUM (time_t))
     {
-      *secs = u;
-      if (*p == '.' && xstrtoumax (p+1, NULL, 10, &u, "") == LONGINT_OK)
-       *nsecs = u;
+      uintmax_t v;
+      if (*p == '\0')
+       {
+         *secs = u;
+         return;
+       }
+      if (*p == '.'
+         && xstrtoumax (p+1, NULL, 10, &v, "") == LONGINT_OK
+         && v <= TYPE_MAXIMUM (unsigned long))
+       {
+         *secs = u;
+         *nsecs = v;
+         return;
+       }
     }
+  FATAL_ERROR ((0, 0, _("Malformed header: invalid time: %s"), arg));
 }
 
 static void
@@ -833,8 +846,12 @@ static void
 gid_decoder (struct tar_stat_info *st, char const *arg)
 {
   uintmax_t u;
-  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK)
+  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK
+      && u <= TYPE_MAXIMUM (gid_t))
     st->stat.st_gid = u;
+  else
+    FATAL_ERROR ((0, 0,
+                 _("Malformed header: invalid group ID number: %s"), arg));
 }
 
 static void
@@ -915,8 +932,11 @@ static void
 size_decoder (struct tar_stat_info *st, char const *arg)
 {
   uintmax_t u;
-  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK)
+  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK
+      && u <= TYPE_MAXIMUM (off_t))
     st->archive_file_size = st->stat.st_size = u;
+  else
+    FATAL_ERROR ((0, 0, _("Malformed header: invalid archive size: %s"), arg));
 }
 
 static void
@@ -930,8 +950,11 @@ static void
 uid_decoder (struct tar_stat_info *st, char const *arg)
 {
   uintmax_t u;
-  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK)
+  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK
+      && u <= TYPE_MAXIMUM (uid_t))
     st->stat.st_uid = u;
+  else
+    FATAL_ERROR ((0, 0, _("Malformed header: invalid user ID number: %s"), 
arg));
 }
 
 static void
@@ -958,8 +981,11 @@ static void
 sparse_size_decoder (struct tar_stat_info *st, char const *arg)
 {
   uintmax_t u;
-  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK)
+  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK
+      && u <= TYPE_MAXIMUM (off_t))
     st->stat.st_size = u;
+  else
+    FATAL_ERROR ((0, 0, _("Malformed header: invalid sparse size: %s"), arg));
 }
 
 static void
@@ -974,12 +1000,15 @@ static void
 sparse_numblocks_decoder (struct tar_stat_info *st, char const *arg)
 {
   uintmax_t u;
-  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK)
+  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK && u <= SIZE_MAX)
     {
       st->sparse_map_size = u;
-      st->sparse_map = calloc(st->sparse_map_size, sizeof(st->sparse_map[0]));
+      st->sparse_map = xcalloc(st->sparse_map_size, sizeof(st->sparse_map[0]));
       st->sparse_map_avail = 0;
     }
+  else
+    FATAL_ERROR ((0, 0,
+       _("Malformed header: invalid number of sparse blocks: %s"), arg));
 }
 
 static void
@@ -994,8 +1023,11 @@ static void
 sparse_offset_decoder (struct tar_stat_info *st, char const *arg)
 {
   uintmax_t u;
-  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK)
+  if (xstrtoumax (arg, NULL, 10, &u, "") == LONGINT_OK
+      && u <= TYPE_MAXIMUM (off_t))
     st->sparse_map[st->sparse_map_avail].offset = u;
+  else
+    FATAL_ERROR ((0, 0, _("Malformed header: invalid sparse offset: %s"), 
arg));
 }
 
 static void
@@ -1015,12 +1047,15 @@ sparse_numbytes_decoder (struct tar_stat
       if (st->sparse_map_avail == st->sparse_map_size)
        {
          st->sparse_map_size *= 2;
-         st->sparse_map = xrealloc (st->sparse_map,
-                                    st->sparse_map_size
-                                    * sizeof st->sparse_map[0]);
+         st->sparse_map = x2nrealloc (st->sparse_map,
+                                      &st->sparse_map_size,
+                                      sizeof st->sparse_map[0]);
        }
       st->sparse_map[st->sparse_map_avail++].numbytes = u;
     }
+  else
+    FATAL_ERROR ((1, 0,
+                 _("Malformed header: invalid sparse byte count: %s"), arg));
 }
 
 struct xhdr_tab const xhdr_tab[] = {




reply via email to

[Prev in Thread] Current Thread [Next in Thread]