Blame SOURCES/file-roller-3.28.1-CVE-2020-36314.patch

89973a
From 23deb42cd555b1e6b174a0bd9eaa814be5bc3558 Mon Sep 17 00:00:00 2001
89973a
From: Ondrej Holy <oholy@redhat.com>
89973a
Date: Thu, 11 Mar 2021 16:24:35 +0100
89973a
Subject: [PATCH] libarchive: Skip files with symlinks in parents
89973a
89973a
Currently, it is still possible that some files are extracted outside of
89973a
the destination dir in case of malicious archives. The checks from commit
89973a
21dfcdbf can be still bypassed in certain cases. See GNOME/file-roller#108
89973a
for more details. After some investigation, I am convinced that it would be
89973a
best to simply disallow symlinks in parents. For example, `tar` fails to
89973a
extract such files with the `ENOTDIR` error. Let's do the same here.
89973a
89973a
Fixes: https://gitlab.gnome.org/GNOME/file-roller/-/issues/108
89973a
---
89973a
 src/fr-archive-libarchive.c | 136 ++++++------------------------------
89973a
 1 file changed, 20 insertions(+), 116 deletions(-)
89973a
89973a
diff --git a/src/fr-archive-libarchive.c b/src/fr-archive-libarchive.c
89973a
index 07babbc4..70d3e763 100644
89973a
--- a/src/fr-archive-libarchive.c
89973a
+++ b/src/fr-archive-libarchive.c
89973a
@@ -600,115 +600,12 @@ _g_output_stream_add_padding (ExtractData    *extract_data,
89973a
 	return success;
89973a
 }
89973a
 
89973a
-
89973a
-static gboolean
89973a
-_symlink_is_external_to_destination (GFile      *file,
89973a
-				     const char *symlink,
89973a
-				     GFile      *destination,
89973a
-				     GHashTable *external_links);
89973a
-
89973a
-
89973a
-static gboolean
89973a
-_g_file_is_external_link (GFile      *file,
89973a
-			  GFile      *destination,
89973a
-			  GHashTable *external_links)
89973a
-{
89973a
-	GFileInfo *info;
89973a
-	gboolean   external;
89973a
-
89973a
-	if (g_hash_table_lookup (external_links, file) != NULL)
89973a
-		return TRUE;
89973a
-
89973a
-	info = g_file_query_info (file,
89973a
-				  G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK "," G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
89973a
-				  G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
89973a
-				  NULL,
89973a
-				  NULL);
89973a
-
89973a
-	if (info == NULL)
89973a
-		return FALSE;
89973a
-
89973a
-	external = FALSE;
89973a
-
89973a
-	if (g_file_info_get_is_symlink (info)) {
89973a
-		if (_symlink_is_external_to_destination (file,
89973a
-							 g_file_info_get_symlink_target (info),
89973a
-							 destination,
89973a
-							 external_links))
89973a
-		{
89973a
-			g_hash_table_insert (external_links, g_object_ref (file), GINT_TO_POINTER (1));
89973a
-			external = TRUE;
89973a
-		}
89973a
-	}
89973a
-
89973a
-	g_object_unref (info);
89973a
-
89973a
-	return external;
89973a
-}
89973a
-
89973a
-
89973a
 static gboolean
89973a
-_symlink_is_external_to_destination (GFile      *file,
89973a
-				     const char *symlink,
89973a
-				     GFile      *destination,
89973a
-				     GHashTable *external_links)
89973a
+_g_file_contains_symlinks_in_path (const char *relative_path,
89973a
+				   GFile      *destination,
89973a
+				   GHashTable *symlinks)
89973a
 {
89973a
-	gboolean  external = FALSE;
89973a
-	GFile    *parent;
89973a
-	char    **components;
89973a
-	int       i;
89973a
-
89973a
-	if ((file == NULL) || (symlink == NULL))
89973a
-		return FALSE;
89973a
-
89973a
-	if (symlink[0] == '/')
89973a
-		return TRUE;
89973a
-
89973a
-	parent = g_file_get_parent (file);
89973a
-	components = g_strsplit (symlink, "/", -1);
89973a
-	for (i = 0; components[i] != NULL; i++) {
89973a
-		char  *name = components[i];
89973a
-		GFile *tmp;
89973a
-
89973a
-		if ((name[0] == 0) || ((name[0] == '.') && (name[1] == 0)))
89973a
-			continue;
89973a
-
89973a
-		if ((name[0] == '.') && (name[1] == '.') && (name[2] == 0)) {
89973a
-			if (g_file_equal (parent, destination)) {
89973a
-				external = TRUE;
89973a
-				break;
89973a
-			}
89973a
-			else {
89973a
-				tmp = g_file_get_parent (parent);
89973a
-				g_object_unref (parent);
89973a
-				parent = tmp;
89973a
-			}
89973a
-		}
89973a
-		else {
89973a
-			tmp = g_file_get_child (parent, components[i]);
89973a
-			g_object_unref (parent);
89973a
-			parent = tmp;
89973a
-		}
89973a
-
89973a
-		if (_g_file_is_external_link (parent, destination, external_links)) {
89973a
-			external = TRUE;
89973a
-			break;
89973a
-		}
89973a
-	}
89973a
-
89973a
-	g_strfreev (components);
89973a
-	g_object_unref (parent);
89973a
-
89973a
-	return external;
89973a
-}
89973a
-
89973a
-
89973a
-static gboolean
89973a
-_g_path_is_external_to_destination (const char *relative_path,
89973a
-				    GFile      *destination,
89973a
-				    GHashTable *external_links)
89973a
-{
89973a
-	gboolean  external = FALSE;
89973a
+	gboolean  contains_symlinks = FALSE;
89973a
 	GFile    *parent;
89973a
 	char    **components;
89973a
 	int       i;
89973a
@@ -731,8 +628,8 @@ _g_path_is_external_to_destination (const char *relative_path,
89973a
 		g_object_unref (parent);
89973a
 		parent = tmp;
89973a
 
89973a
-		if (_g_file_is_external_link (parent, destination, external_links)) {
89973a
-			external = TRUE;
89973a
+		if (g_hash_table_contains (symlinks, parent)) {
89973a
+			contains_symlinks = TRUE;
89973a
 			break;
89973a
 		}
89973a
 	}
89973a
@@ -740,7 +637,7 @@ _g_path_is_external_to_destination (const char *relative_path,
89973a
 	g_strfreev (components);
89973a
 	g_object_unref (parent);
89973a
 
89973a
-	return external;
89973a
+	return contains_symlinks;
89973a
 }
89973a
 
89973a
 
89973a
@@ -754,7 +651,7 @@ extract_archive_thread (GSimpleAsyncResult *result,
89973a
 	GHashTable           *checked_folders;
89973a
 	GHashTable           *created_files;
89973a
 	GHashTable           *folders_created_during_extraction;
89973a
-	GHashTable           *external_links;
89973a
+	GHashTable           *symlinks;
89973a
 	struct archive       *a;
89973a
 	struct archive_entry *entry;
89973a
 	int                   r;
89973a
@@ -765,7 +662,7 @@ extract_archive_thread (GSimpleAsyncResult *result,
89973a
 	checked_folders = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
89973a
 	created_files = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, g_object_unref);
89973a
 	folders_created_during_extraction = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
89973a
-	external_links = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
89973a
+	symlinks = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
89973a
 	fr_archive_progress_set_total_files (load_data->archive, extract_data->n_files_to_extract);
89973a
 
89973a
 	a = archive_read_new ();
89973a
@@ -803,7 +700,14 @@ extract_archive_thread (GSimpleAsyncResult *result,
89973a
 			continue;
89973a
 		}
89973a
 
89973a
-		if (_g_path_is_external_to_destination (relative_path, extract_data->destination, external_links)) {
89973a
+		/* Symlinks in parents are dangerous as it can easily happen
89973a
+		 * that files are written outside of the destination. The tar
89973a
+		 * cmd fails to extract such archives with ENOTDIR. Let's skip
89973a
+		 * those files here for sure. This is most probably malicious,
89973a
+		 * or corrupted archive.
89973a
+		 */
89973a
+		if (_g_file_contains_symlinks_in_path (relative_path, extract_data->destination, symlinks)) {
89973a
+			g_warning ("Skipping '%s' file as it has symlink in parents.", relative_path);
89973a
 			fr_archive_progress_inc_completed_files (load_data->archive, 1);
89973a
 			fr_archive_progress_inc_completed_bytes (load_data->archive, archive_entry_size_is_set (entry) ? archive_entry_size (entry) : 0);
89973a
 			archive_read_data_skip (a);
89973a
@@ -1024,8 +928,8 @@ extract_archive_thread (GSimpleAsyncResult *result,
89973a
 						load_data->error = g_error_copy (local_error);
89973a
 					g_clear_error (&local_error);
89973a
 				}
89973a
-				if ((load_data->error == NULL) && _symlink_is_external_to_destination (file, archive_entry_symlink (entry), extract_data->destination, external_links))
89973a
-					g_hash_table_insert (external_links, g_object_ref (file), GINT_TO_POINTER (1));
89973a
+				if (load_data->error == NULL)
89973a
+					g_hash_table_add (symlinks, g_object_ref (file));
89973a
 				archive_read_data_skip (a);
89973a
 				break;
89973a
 
89973a
@@ -1060,7 +964,7 @@ extract_archive_thread (GSimpleAsyncResult *result,
89973a
 	g_hash_table_unref (folders_created_during_extraction);
89973a
 	g_hash_table_unref (created_files);
89973a
 	g_hash_table_unref (checked_folders);
89973a
-	g_hash_table_unref (external_links);
89973a
+	g_hash_table_unref (symlinks);
89973a
 	archive_read_free (a);
89973a
 	extract_data_free (extract_data);
89973a
 }
89973a
-- 
89973a
2.31.1
89973a