bug-tar
[Top][All Lists]
Advanced

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

[Bug-tar] [PATCH] Option to reject files similar to tarbombs


From: Connor Behan
Subject: [Bug-tar] [PATCH] Option to reject files similar to tarbombs
Date: Sat, 21 Sep 2013 02:15:57 -0700

A common annoyance in the free software world is an archive that leaves
several top-level files in the working directory after being extracted.
However, this is a special case of a more general annoyance, where files
can also be extracted to paths above the working directory. This adds an
option causing tar to bail in mid-extraction as soon as it determines
that the archive is of the type mentioned above. It would be up to the
user to subsequently apply shell commands to contain these archives.

Signed-off-by: Connor Behan <address@hidden>
---
 NEWS          | 10 ++++++++++
 doc/tar.texi  |  8 ++++++++
 src/common.h  |  4 ++++
 src/extract.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/tar.c     |  7 +++++++
 5 files changed, 93 insertions(+)

diff --git a/NEWS b/NEWS
index 3108798..a07bca1 100644
--- a/NEWS
+++ b/NEWS
@@ -39,6 +39,16 @@ errors.  Instead it just silently skips them.  An additional 
level of
 verbosity can be obtained by using the option --warning=existing-file
 together with this option.
 
+* The --single-top-level option.
+
+This new command line option instructs tar to ensure that the extracted
+contents of an archive will be contained in the working directory
+(or the directory passed to -C) without having multiple names directly
+under it. Three ways for the check to fail are: files above the working
+directory because of a leading slash, files above the working directory
+because of '..' and multiple files at the top-level (colloquially known
+as a tarbomb).
+
 * Support for POSIX ACLs, extended attributes and SELinux context.
 
 Starting with this version tar is able to store, extract and list
diff --git a/doc/tar.texi b/doc/tar.texi
index ddfa055..27d61e3 100644
--- a/doc/tar.texi
+++ b/doc/tar.texi
@@ -3277,6 +3277,14 @@ the archive creation operations it instructs 
@command{tar} to list the
 member names stored in the archive, as opposed to the actual file
 names.  @xref{listing member and file names}.
 
address@hidden
address@hidden --single-top-level
+
+Tells @command{tar} not to touch anything above the extraction directory (the
+working directory unless another one is specified with @option{-C}) and to
+extract only one name directly below it. A fatal error is thrown if the archive
+contains more than one top-level name or any names above the top-level.
+
 @opsummary{skip-old-files}
 @item --skip-old-files
 
diff --git a/src/common.h b/src/common.h
index 723ad90..af5e5c5 100644
--- a/src/common.h
+++ b/src/common.h
@@ -270,6 +270,10 @@ GLOBAL size_t strip_name_components;
 
 GLOBAL bool show_omitted_dirs_option;
 
+/* Refuse to extract names above the extraction directory or more than one name
+   that is one level below it.  */
+GLOBAL bool single_top_level_option;
+
 GLOBAL bool sparse_option;
 GLOBAL unsigned tar_sparse_major;
 GLOBAL unsigned tar_sparse_minor;
diff --git a/src/extract.c b/src/extract.c
index 3d8ba10..e6125eb 100644
--- a/src/extract.c
+++ b/src/extract.c
@@ -1559,6 +1559,66 @@ prepare_to_extract (char const *file_name, int typeflag, 
tar_extractor_t *fun)
   return 1;
 }
 
+/* Throws an error if passed file name is the second name at the top-level
+   or the first name above it.  */
+void
+depth_check (char *file_name)
+{
+  int i;
+  int start = 0;
+  int depth = 0;
+  int length = strlen (file_name);
+  char *token = xmalloc (length + 1);
+
+  char *top_level;
+  static char *first_top_level;
+
+  bool top_level_found = false;
+  static bool first_top_level_found = false;
+
+  if (ISSLASH (file_name[0]))
+    FATAL_ERROR ((0, 0, _("Found name with leading slash, exiting")));
+
+  for (i = 1; i <= length; i++)
+    {
+      if (ISSLASH (file_name[i]) || file_name[i] == '\0')
+        {
+         strncpy (token, file_name + start, i - start + 1);
+         token[i - start] = '\0';
+
+         if (!strcmp (token, ".."))
+           depth--;
+         else if (strcmp (token, "."))
+           depth++;
+
+         if (depth < 0)
+           FATAL_ERROR ((0, 0, _("Found name outside desired directory, 
exiting")));
+         else if (depth == 1)
+           {
+             top_level = strdup (token);
+             top_level_found = true;
+           }
+
+         start = i + 1;
+       }
+    }
+
+  if (top_level_found)
+    {
+      if (first_top_level_found && strcmp (top_level, first_top_level))
+        FATAL_ERROR ((0, 0, _("Found multiple top-level names, exiting")));
+      else if (first_top_level_found)
+        free (top_level);
+      else
+        {
+         first_top_level = top_level;
+         first_top_level_found = true;
+       }
+    }
+
+  free (token);
+}
+
 /* Extract a file from the archive.  */
 void
 extract_archive (void)
@@ -1604,6 +1664,10 @@ extract_archive (void)
        return;
       }
 
+  /* Check for names where the depth is too high.  */
+  if (single_top_level_option)
+      depth_check (current_stat_info.file_name);
+
   /* Extract the archive entry according to its type.  */
   /* KLUDGE */
   typeflag = sparse_member_p (&current_stat_info) ?
diff --git a/src/tar.c b/src/tar.c
index 5a4ed60..6dff74b 100644
--- a/src/tar.c
+++ b/src/tar.c
@@ -339,6 +339,7 @@ enum
   SHOW_OMITTED_DIRS_OPTION,
   SHOW_SNAPSHOT_FIELD_RANGES_OPTION,
   SHOW_TRANSFORMED_NAMES_OPTION,
+  SINGLE_TOP_LEVEL_OPTION,
   SKIP_OLD_FILES_OPTION,
   SPARSE_VERSION_OPTION,
   STRIP_COMPONENTS_OPTION,
@@ -485,6 +486,8 @@ static struct argp_option options[] = {
   {"overwrite-dir", OVERWRITE_DIR_OPTION, 0, 0,
    N_("overwrite metadata of existing directories when extracting (default)"),
    GRID+1 },
+  {"single-top-level", SINGLE_TOP_LEVEL_OPTION, 0, 0,
+   N_("allow at most one top-level name when extracting"), GRID+1 },
 #undef GRID
 
 #define GRID 40
@@ -1551,6 +1554,10 @@ parse_opt (int key, char *arg, struct argp_state *state)
       sparse_option = true;
       break;
 
+    case SINGLE_TOP_LEVEL_OPTION:
+      single_top_level_option = true;
+      break;
+
     case SKIP_OLD_FILES_OPTION:
       old_files_option = SKIP_OLD_FILES;
       break;
-- 
1.8.4




reply via email to

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