Skip to content

Commit

Permalink
repo: honor safe.directory during ownership checks
Browse files Browse the repository at this point in the history
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.)
  • Loading branch information
ethomson committed Apr 12, 2022
1 parent f683806 commit eb8c3e5
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 5 deletions.
51 changes: 46 additions & 5 deletions src/repository.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 load_global_config(git_config **config);

#define GIT_COMMONDIR_FILE "commondir"
#define GIT_GITDIR_FILE "gitdir"
Expand Down Expand Up @@ -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;
}

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_buf_dispose(&data.tmp);
return error;
}

static int find_repo(
Expand Down
102 changes: 102 additions & 0 deletions tests/repo/open.c
Original file line number Diff line number Diff line change
Expand Up @@ -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_dispose(&config_path);
}

void test_repo_open__bare_empty_repo(void)
Expand Down Expand Up @@ -480,11 +490,103 @@ 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"));

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_dispose(&config_path);
git_buf_dispose(&config_filename);
git_buf_dispose(&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_dispose(&config_path);
git_buf_dispose(&config_filename);
git_buf_dispose(&config_data);
}

0 comments on commit eb8c3e5

Please sign in to comment.