File 0006-repo-honor-safe.directory-during-ownership-checks.patch of Package libgit2.25920

From eb8c3e5dabdeaeb51d8fea39545b4c0c9ddff07a Mon Sep 17 00:00:00 2001
From: Edward Thomson <ethomson@edwardthomson.com>
Date: Mon, 11 Apr 2022 15:18:44 -0400
Subject: [PATCH 06/20] repo: honor safe.directory during ownership checks

Obey the `safe.directory` configuration variable if it is set in the
global or system configuration. (Do not try to load this from the
repository configuration - to avoid malicious repositories that then
mark themselves as safe.)
---
 src/repository.c  |  51 ++++++++++++++++++++---
 tests/repo/open.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 148 insertions(+), 5 deletions(-)

diff --git a/src/repository.c b/src/repository.c
index e8ea1e75c..ac2581167 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -64,6 +64,7 @@ static const struct {
# 
# static int check_repositoryformatversion(int *version, git_config *config);
# static int check_extensions(git_config *config, int version);
 };
 
 static int check_repositoryformatversion(git_config *config);
+static int load_global_config(git_config **config);
 
 #define GIT_COMMONDIR_FILE "commondir"
 #define GIT_GITDIR_FILE "gitdir"
@@ -482,21 +483,61 @@ static int read_gitfile(git_buf *path_out, const char *file_path)
 	return error;
 }
 
+typedef struct {
+	const char *repo_path;
+	git_buf tmp;
+	bool is_safe;
+} validate_ownership_data;
+
+static int validate_ownership_cb(const git_config_entry *entry, void *payload)
+{
+	validate_ownership_data *data = payload;
+
+	if (strcmp(entry->value, "") == 0)
+		data->is_safe = false;
+
+	if (git_path_prettify_dir(&data->tmp, entry->value, NULL) == 0 &&
+	    strcmp(data->tmp.ptr, data->repo_path) == 0)
+		data->is_safe = true;
+
+	return 0;
+}
+
 static int validate_ownership(const char *repo_path)
 {
+	git_config *config = NULL;
+	validate_ownership_data data = { repo_path, GIT_BUF_INIT, false };
 	bool is_safe;
 	int error;
 
-	if ((error = git_path_owner_is_current_user(&is_safe, repo_path)) < 0)
-		return (error == GIT_ENOTFOUND) ? 0 : error;
+	if ((error = git_path_owner_is_current_user(&is_safe, repo_path)) < 0) {
+		if (error == GIT_ENOTFOUND)
+			error = 0;
 
-	if (is_safe)
-		return 0;
+		goto done;
+	}
+
+	if (is_safe) {
+		error = 0;
+		goto done;
+	}
+
+	if (load_global_config(&config) == 0) {
+		error = git_config_get_multivar_foreach(config, "safe.directory", NULL, validate_ownership_cb, &data);
+
+		if (!error && data.is_safe)
+			goto done;
+	}
 
# 	giterr_set(GIT_ERROR_CONFIG,
 	giterr_set(GITERR_CONFIG,
 		"repository path '%s' is not owned by current user",
 		repo_path);
-	return GIT_EOWNER;
+	error = GIT_EOWNER;
+
+done:
+	git_config_free(config);
+	git_buf_free(&data.tmp);
+	return error;
 }
 
 static int find_repo(
diff --git a/tests/repo/open.c b/tests/repo/open.c
index c7e7a4ccf..f23ba1c18 100644
--- a/tests/repo/open.c
+++ b/tests/repo/open.c
@@ -3,16 +3,26 @@
 #include "sysdir.h"
 #include <ctype.h>
 
+static git_buf config_path = GIT_BUF_INIT;
+
+void test_repo_open__initialize(void)
+{
+	cl_git_pass(git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &config_path));
+}
 
 void test_repo_open__cleanup(void)
 {
 	cl_git_sandbox_cleanup();
 	cl_fixture_cleanup("empty_standard_repo");
+	cl_fixture_cleanup("__global_config");
 
 	if (git_path_isdir("alternate"))
 		git_futils_rmdir_r("alternate", NULL, GIT_RMDIR_REMOVE_FILES);
 
 	git_path__set_owner(GIT_PATH_MOCK_OWNER_NONE);
+
+	cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, config_path.ptr));
+	git_buf_free(&config_path);
 }
 
 void test_repo_open__bare_empty_repo(void)
@@ -480,6 +490,9 @@ void test_repo_open__validates_dir_ownership(void)
 void test_repo_open__can_allowlist_dirs_with_problematic_ownership(void)
 {
 	git_repository *repo;
+	git_buf config_path = GIT_BUF_INIT,
+	        config_filename = GIT_BUF_INIT,
+	        config_data = GIT_BUF_INIT;
 
 	cl_fixture_sandbox("empty_standard_repo");
 	cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git"));
@@ -487,4 +500,93 @@ void test_repo_open__can_allowlist_dirs_with_problematic_ownership(void)
 	git_path__set_owner(GIT_PATH_MOCK_OWNER_OTHER);
 	cl_git_fail(git_repository_open(&repo, "empty_standard_repo"));
 
+	/* Add safe.directory options to the global configuration */
+	git_buf_joinpath(&config_path, clar_sandbox_path(), "__global_config");
+	cl_must_pass(p_mkdir(config_path.ptr, 0777));
+	git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, config_path.ptr);
+
+	git_buf_joinpath(&config_filename, config_path.ptr, ".gitconfig");
+
+	git_buf_printf(&config_data,
+		"[foo]\n" \
+		"\tbar = Foobar\n" \
+		"\tbaz = Baz!\n" \
+		"[safe]\n" \
+		"\tdirectory = /non/existent/path\n" \
+		"\tdirectory = /\n" \
+		"\tdirectory = c:\\\\temp\n" \
+		"\tdirectory = %s/%s\n" \
+		"\tdirectory = /tmp\n" \
+		"[bar]\n" \
+		"\tfoo = barfoo\n",
+		clar_sandbox_path(), "empty_standard_repo");
+	cl_git_rewritefile(config_filename.ptr, config_data.ptr);
+
+	cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+	git_repository_free(repo);
+
+	git_buf_free(&config_path);
+	git_buf_free(&config_filename);
+	git_buf_free(&config_data);
+}
+
+void test_repo_open__can_reset_safe_directory_list(void)
+{
+	git_repository *repo;
+	git_buf config_path = GIT_BUF_INIT,
+	        config_filename = GIT_BUF_INIT,
+	        config_data = GIT_BUF_INIT;
+
+	cl_fixture_sandbox("empty_standard_repo");
+	cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git"));
+
+	git_path__set_owner(GIT_PATH_MOCK_OWNER_OTHER);
+	cl_git_fail(git_repository_open(&repo, "empty_standard_repo"));
+
+	/* Add safe.directory options to the global configuration */
+	git_buf_joinpath(&config_path, clar_sandbox_path(), "__global_config");
+	cl_must_pass(p_mkdir(config_path.ptr, 0777));
+	git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, config_path.ptr);
+
+	git_buf_joinpath(&config_filename, config_path.ptr, ".gitconfig");
+
+	/* The blank resets our sandbox directory and opening fails */
+
+	git_buf_printf(&config_data,
+		"[foo]\n" \
+		"\tbar = Foobar\n" \
+		"\tbaz = Baz!\n" \
+		"[safe]\n" \
+		"\tdirectory = %s/%s\n" \
+		"\tdirectory = \n" \
+		"\tdirectory = /tmp\n" \
+		"[bar]\n" \
+		"\tfoo = barfoo\n",
+		clar_sandbox_path(), "empty_standard_repo");
+	cl_git_rewritefile(config_filename.ptr, config_data.ptr);
+
+	cl_git_fail(git_repository_open(&repo, "empty_standard_repo"));
+
+	/* The blank resets tmp and allows subsequent declarations to succeed */
+
+	git_buf_clear(&config_data);
+	git_buf_printf(&config_data,
+		"[foo]\n" \
+		"\tbar = Foobar\n" \
+		"\tbaz = Baz!\n" \
+		"[safe]\n" \
+		"\tdirectory = /tmp\n" \
+		"\tdirectory = \n" \
+		"\tdirectory = %s/%s\n" \
+		"[bar]\n" \
+		"\tfoo = barfoo\n",
+		clar_sandbox_path(), "empty_standard_repo");
+	cl_git_rewritefile(config_filename.ptr, config_data.ptr);
+
+	cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+	git_repository_free(repo);
+
+	git_buf_free(&config_path);
+	git_buf_free(&config_filename);
+	git_buf_free(&config_data);
 }
-- 
2.37.1

openSUSE Build Service is sponsored by