Headline
CVE-2020-11736: libarchive: do not follow external links when extracting files (21dfcdbf) · Commits · GNOME / File Roller
fr-archive-libarchive.c in GNOME file-roller through 3.36.1 allows Directory Traversal during extraction because it lacks a check of whether a file’s parent is a symlink to a directory outside of the intended extraction location.
…
…
@@ -698,6 +698,149 @@ _g_output_stream_add_padding (ExtractData *extract_data,
}
static gboolean
_symlink_is_external_to_destination (GFile *file,
const char *symlink,
GFile *destination,
GHashTable *external_links);
static gboolean
_g_file_is_external_link (GFile *file,
GFile *destination,
GHashTable *external_links)
{
GFileInfo *info;
gboolean external;
if (g_hash_table_lookup (external_links, file) != NULL)
return TRUE;
info = g_file_query_info (file,
G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK “,” G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
NULL,
NULL);
if (info == NULL)
return FALSE;
external = FALSE;
if (g_file_info_get_is_symlink (info)) {
if (_symlink_is_external_to_destination (file,
g_file_info_get_symlink_target (info),
destination,
external_links))
{
g_hash_table_insert (external_links, g_object_ref (file), GINT_TO_POINTER (1));
external = TRUE;
}
}
g_object_unref (info);
return external;
}
static gboolean
_symlink_is_external_to_destination (GFile *file,
const char *symlink,
GFile *destination,
GHashTable *external_links)
{
gboolean external = FALSE;
GFile *parent;
char **components;
int i;
if ((file == NULL) || (symlink == NULL))
return FALSE;
if (symlink[0] == ‘/’)
return TRUE;
parent = g_file_get_parent (file);
components = g_strsplit (symlink, "/", -1);
for (i = 0; components[i] != NULL; i++) {
char *name = components[i];
GFile *tmp;
if ((name[0] == 0) || ((name[0] == ‘.’) && (name[1] == 0)))
continue;
if ((name[0] == ‘.’) && (name[1] == ‘.’) && (name[2] == 0)) {
if (g_file_equal (parent, destination)) {
external = TRUE;
break;
}
else {
tmp = g_file_get_parent (parent);
g_object_unref (parent);
parent = tmp;
}
}
else {
tmp = g_file_get_child (parent, components[i]);
g_object_unref (parent);
parent = tmp;
}
if (_g_file_is_external_link (parent, destination, external_links)) {
external = TRUE;
break;
}
}
g_strfreev (components);
g_object_unref (parent);
return external;
}
static gboolean
_g_path_is_external_to_destination (const char *relative_path,
GFile *destination,
GHashTable *external_links)
{
gboolean external = FALSE;
GFile *parent;
char **components;
int i;
if (relative_path == NULL)
return FALSE;
if (destination == NULL)
return TRUE;
parent = g_object_ref (destination);
components = g_strsplit (relative_path, "/", -1);
for (i = 0; (components[i] != NULL) && (components[i + 1] != NULL); i++) {
GFile *tmp;
if (components[i][0] == 0)
continue;
tmp = g_file_get_child (parent, components[i]);
g_object_unref (parent);
parent = tmp;
if (_g_file_is_external_link (parent, destination, external_links)) {
external = TRUE;
break;
}
}
g_strfreev (components);
g_object_unref (parent);
return external;
}
static void
extract_archive_thread (GSimpleAsyncResult *result,
GObject *object,
…
…
@@ -708,6 +851,7 @@ extract_archive_thread (GSimpleAsyncResult *result,
GHashTable *checked_folders;
GHashTable *created_files;
GHashTable *folders_created_during_extraction;
GHashTable *external_links;
struct archive *a;
struct archive_entry *entry;
int r;
…
…
@@ -724,6 +868,7 @@ extract_archive_thread (GSimpleAsyncResult *result,
checked_folders = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
created_files = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, g_object_unref);
folders_created_during_extraction = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
external_links = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
fr_archive_progress_set_total_files (load_data->archive, extract_data->n_files_to_extract);
while ((r = archive_read_next_header (a, &entry)) == ARCHIVE_OK) {
…
…
@@ -751,6 +896,15 @@ extract_archive_thread (GSimpleAsyncResult *result,
fullpath = (*pathname == ‘/’) ? g_strdup (pathname) : g_strconcat ("/", pathname, NULL);
relative_path = _g_path_get_relative_basename_safe (fullpath, extract_data->base_dir, extract_data->junk_paths);
if (relative_path == NULL) {
fr_archive_progress_inc_completed_files (load_data->archive, 1);
fr_archive_progress_inc_completed_bytes (load_data->archive, archive_entry_size_is_set (entry) ? archive_entry_size (entry) : 0);
archive_read_data_skip (a);
continue;
}
if (_g_path_is_external_to_destination (relative_path, extract_data->destination, external_links)) {
fr_archive_progress_inc_completed_files (load_data->archive, 1);
fr_archive_progress_inc_completed_bytes (load_data->archive, archive_entry_size_is_set (entry) ? archive_entry_size (entry) : 0);
archive_read_data_skip (a);
continue;
}
…
…
@@ -959,6 +1113,8 @@ extract_archive_thread (GSimpleAsyncResult *result,
load_data->error = g_error_copy (local_error);
g_clear_error (&local_error);
}
else if (_symlink_is_external_to_destination (file, archive_entry_symlink (entry), extract_data->destination, external_links))
g_hash_table_insert (external_links, g_object_ref (file), GINT_TO_POINTER (1));
archive_read_data_skip (a);
break;
…
…
@@ -993,6 +1149,7 @@ extract_archive_thread (GSimpleAsyncResult *result,
g_hash_table_unref (folders_created_during_extraction);
g_hash_table_unref (created_files);
g_hash_table_unref (checked_folders);
g_hash_table_unref (external_links);
archive_read_free (a);
extract_data_free (extract_data);
}
…
…