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

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