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

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