Detect tarbombs while extracting

* src/common.h (one_top_level_option): New global.
(one_top_level): New global.
* src/extract.c (extr_init): If one_top_level_option is set, determine
the name one_top_level that might have to be prepended.
(extract_archive): If one_top_level_option is set, prepend one_top_level
to all names that don't already start with it.
* src/tar.c (ONE_TOP_LEVEL_OPTION): New contant.
(options): New option --one-top-level.
(parse_opt): Handle this option.
(decode_options): Make it conflict with --absolute-names.
This commit is contained in:
Connor Behan
2014-01-27 14:42:09 +02:00
committed by Sergey Poznyakoff
parent 95a51b93d0
commit 2af87fa277
5 changed files with 93 additions and 0 deletions

8
NEWS
View File

@@ -42,6 +42,14 @@ version 1.27.1 - Sergey Poznyakoff, 2013-11-17
* Fix extracting sparse members from star archives.
* The --one-top-level option.
This new command line option tells tar that the working directory
(or the one passed to -C) should not be populated with more than one
name directly under it. Instead, a newly created subdirectory is
used whose name is equal to the archive name without the extension.
For example, foo.tar.gz would be extracted to foo.
version 1.27 - Sergey Poznyakoff, 2013-10-05

View File

@@ -3086,6 +3086,17 @@ Used when creating an archive. Prevents @command{tar} from recursing into
directories that are on different file systems from the current
directory.
@opsummary{one-top-level}
@item --one-top-level
Tells @command{tar} to create a new directory beneath the extraction directory
(or the one passed to @option{-C}) and use it to guard against tarbombs. The
name of the new directory will be equal to the name of the archive with the
extension stripped off. If any archive names (after transformations from
@option{--transform} and @option{--strip-components}) do not already begin with
it, the new directory will be prepended to the names immediately before
extraction. Recognized extensions are @samp{.tar}, @samp{.taz}, @samp{.tbz},
@samp{.tb2}, @samp{.tgz}, @samp{.tlz} and @samp{.txz}.
@opsummary{overwrite}
@item --overwrite

View File

@@ -235,6 +235,10 @@ GLOBAL bool numeric_owner_option;
GLOBAL bool one_file_system_option;
/* Create a top-level directory for extracting based on the archive name. */
GLOBAL bool one_top_level_option;
GLOBAL char *one_top_level;
/* Specified value to be put into tar file in place of stat () results, or
just null and -1 if such an override should not take place. */
GLOBAL char const *owner_name_option;

View File

@@ -191,6 +191,35 @@ extr_init (void)
umask (newdir_umask); /* restore the kernel umask */
current_umask = newdir_umask;
}
/* If the user wants to guarantee that everything is under one directory,
determine its name now and let it be created later. */
if (one_top_level_option)
{
int i;
char *base = base_name (archive_name_array[0]);
for (i = strlen (base) - 1; i > 2; i--)
if (!strncmp (base + i - 3, ".tar", 4) ||
!strncmp (base + i - 3, ".taz", 4) ||
!strncmp (base + i - 3, ".tbz", 4) ||
!strncmp (base + i - 3, ".tb2", 4) ||
!strncmp (base + i - 3, ".tgz", 4) ||
!strncmp (base + i - 3, ".tlz", 4) ||
!strncmp (base + i - 3, ".txz", 4)) break;
if (i <= 3)
{
one_top_level_option = false;
free (base);
return;
}
one_top_level = xmalloc (i - 2);
strncpy (one_top_level, base, i - 3);
one_top_level[i - 3] = '\0';
free (base);
}
}
/* Use fchmod if possible, fchmodat otherwise. */
@@ -1578,6 +1607,33 @@ prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun)
return 1;
}
void
maybe_prepend_name (char **file_name)
{
int i;
for (i = 0; i < strlen (*file_name); i++)
if (!ISSLASH ((*file_name)[i]) && (*file_name)[i] != '.') break;
if (i == strlen (*file_name))
return;
if (!strncmp (*file_name + i, one_top_level, strlen (one_top_level)))
{
int pos = i + strlen (one_top_level);
if (ISSLASH ((*file_name)[pos]) || (*file_name)[pos] == '\0') return;
}
char *new_name = xmalloc (strlen (one_top_level) + strlen (*file_name) + 2);
strcpy (new_name, one_top_level);
strcat (new_name, "/");
strcat (new_name, *file_name);
free (*file_name);
*file_name = new_name;
}
/* Extract a file from the archive. */
void
extract_archive (void)
@@ -1628,6 +1684,9 @@ extract_archive (void)
typeflag = sparse_member_p (&current_stat_info) ?
GNUTYPE_SPARSE : current_header->header.typeflag;
if (one_top_level_option)
maybe_prepend_name (&current_stat_info.file_name);
if (prepare_to_extract (current_stat_info.file_name, typeflag, &fun))
{
if (fun && (*fun) (current_stat_info.file_name, typeflag)

View File

@@ -319,6 +319,7 @@ enum
OCCURRENCE_OPTION,
OLD_ARCHIVE_OPTION,
ONE_FILE_SYSTEM_OPTION,
ONE_TOP_LEVEL_OPTION,
OVERWRITE_DIR_OPTION,
OVERWRITE_OPTION,
OWNER_OPTION,
@@ -489,6 +490,9 @@ static struct argp_option options[] = {
{"keep-directory-symlink", KEEP_DIRECTORY_SYMLINK_OPTION, 0, 0,
N_("preserve existing symlinks to directories when extracting"),
GRID+1 },
{"one-top-level", ONE_TOP_LEVEL_OPTION, 0, 0,
N_("create a subdirectory to avoid having loose files extracted"),
GRID+1 },
#undef GRID
#define GRID 40
@@ -1441,6 +1445,10 @@ parse_opt (int key, char *arg, struct argp_state *state)
one_file_system_option = true;
break;
case ONE_TOP_LEVEL_OPTION:
one_top_level_option = true;
break;
case 'l':
check_links_option = 1;
break;
@@ -2393,6 +2401,9 @@ decode_options (int argc, char **argv)
subcommand_string (subcommand_option)));
}
if (one_top_level_option && absolute_names_option)
USAGE_ERROR ((0, 0, _("--one-top-level cannot be used with --absolute-names")));
if (archive_names == 0)
{
/* If no archive file name given, try TAPE from the environment, or