summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2022-04-11 15:18:44 -0400
committerEdward Thomson <ethomson@edwardthomson.com>2022-04-12 09:06:01 -0400
commit0fe9e5fd05d2f2da7582390e553ab039346396eb (patch)
treef487ee2f2611ac6c59f39406bd0b5be222e0eb32
parentb6cd753bd98abdcc30d92633faa3a1630863ed9c (diff)
downloadlibgit2-0fe9e5fd05d2f2da7582390e553ab039346396eb.tar.gz
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.)
-rw-r--r--src/repository.c51
-rw-r--r--tests/repo/open.c102
2 files changed, 148 insertions, 5 deletions
diff --git a/src/repository.c b/src/repository.c
index 7750be3e9..badb9ddb7 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -65,6 +65,7 @@ static const struct {
static int check_repositoryformatversion(int *version, git_config *config);
static int check_extensions(git_config *config, int version);
+static int load_global_config(git_config **config);
#define GIT_COMMONDIR_FILE "commondir"
#define GIT_GITDIR_FILE "gitdir"
@@ -483,21 +484,61 @@ static int read_gitfile(git_str *path_out, const char *file_path)
return error;
}
+typedef struct {
+ const char *repo_path;
+ git_str 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_fs_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_STR_INIT, false };
bool is_safe;
int error;
- if ((error = git_fs_path_owner_is_current_user(&is_safe, repo_path)) < 0)
- return (error == GIT_ENOTFOUND) ? 0 : error;
+ if ((error = git_fs_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;
+ }
git_error_set(GIT_ERROR_CONFIG,
"repository path '%s' is not owned by current user",
repo_path);
- return GIT_EOWNER;
+ error = GIT_EOWNER;
+
+done:
+ git_config_free(config);
+ git_str_dispose(&data.tmp);
+ return error;
}
static int find_repo(
diff --git a/tests/repo/open.c b/tests/repo/open.c
index fa6e36b8f..7fe366806 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_fs_path_isdir("alternate"))
git_futils_rmdir_r("alternate", NULL, GIT_RMDIR_REMOVE_FILES);
git_fs_path__set_owner(GIT_FS_PATH_MOCK_OWNER_NONE);
+
+ cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, config_path.ptr));
+ git_buf_dispose(&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_str config_path = GIT_STR_INIT,
+ config_filename = GIT_STR_INIT,
+ config_data = GIT_STR_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_fs_path__set_owner(GIT_FS_PATH_MOCK_OWNER_OTHER);
cl_git_fail(git_repository_open(&repo, "empty_standard_repo"));
+ /* Add safe.directory options to the global configuration */
+ git_str_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_str_joinpath(&config_filename, config_path.ptr, ".gitconfig");
+
+ git_str_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_str_dispose(&config_path);
+ git_str_dispose(&config_filename);
+ git_str_dispose(&config_data);
+}
+
+void test_repo_open__can_reset_safe_directory_list(void)
+{
+ git_repository *repo;
+ git_str config_path = GIT_STR_INIT,
+ config_filename = GIT_STR_INIT,
+ config_data = GIT_STR_INIT;
+
+ cl_fixture_sandbox("empty_standard_repo");
+ cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git"));
+
+ git_fs_path__set_owner(GIT_FS_PATH_MOCK_OWNER_OTHER);
+ cl_git_fail(git_repository_open(&repo, "empty_standard_repo"));
+
+ /* Add safe.directory options to the global configuration */
+ git_str_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_str_joinpath(&config_filename, config_path.ptr, ".gitconfig");
+
+ /* The blank resets our sandbox directory and opening fails */
+
+ git_str_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_str_clear(&config_data);
+ git_str_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_str_dispose(&config_path);
+ git_str_dispose(&config_filename);
+ git_str_dispose(&config_data);
}