Skip to content

Commit fa36692

Browse files
committed
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.)
1 parent f7f7e83 commit fa36692

File tree

2 files changed

+148
-5
lines changed

2 files changed

+148
-5
lines changed

src/libgit2/repository.c

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ static const struct {
6565

6666
static int check_repositoryformatversion(int *version, git_config *config);
6767
static int check_extensions(git_config *config, int version);
68+
static int load_global_config(git_config **config);
6869

6970
#define GIT_COMMONDIR_FILE "commondir"
7071
#define GIT_GITDIR_FILE "gitdir"
@@ -483,21 +484,61 @@ static int read_gitfile(git_str *path_out, const char *file_path)
483484
return error;
484485
}
485486

487+
typedef struct {
488+
const char *repo_path;
489+
git_str tmp;
490+
bool is_safe;
491+
} validate_ownership_data;
492+
493+
static int validate_ownership_cb(const git_config_entry *entry, void *payload)
494+
{
495+
validate_ownership_data *data = payload;
496+
497+
if (strcmp(entry->value, "") == 0)
498+
data->is_safe = false;
499+
500+
if (git_fs_path_prettify_dir(&data->tmp, entry->value, NULL) == 0 &&
501+
strcmp(data->tmp.ptr, data->repo_path) == 0)
502+
data->is_safe = true;
503+
504+
return 0;
505+
}
506+
486507
static int validate_ownership(const char *repo_path)
487508
{
509+
git_config *config = NULL;
510+
validate_ownership_data data = { repo_path, GIT_STR_INIT, false };
488511
bool is_safe;
489512
int error;
490513

491-
if ((error = git_fs_path_owner_is_current_user(&is_safe, repo_path)) < 0)
492-
return (error == GIT_ENOTFOUND) ? 0 : error;
514+
if ((error = git_fs_path_owner_is_current_user(&is_safe, repo_path)) < 0) {
515+
if (error == GIT_ENOTFOUND)
516+
error = 0;
493517

494-
if (is_safe)
495-
return 0;
518+
goto done;
519+
}
520+
521+
if (is_safe) {
522+
error = 0;
523+
goto done;
524+
}
525+
526+
if (load_global_config(&config) == 0) {
527+
error = git_config_get_multivar_foreach(config, "safe.directory", NULL, validate_ownership_cb, &data);
528+
529+
if (!error && data.is_safe)
530+
goto done;
531+
}
496532

497533
git_error_set(GIT_ERROR_CONFIG,
498534
"repository path '%s' is not owned by current user",
499535
repo_path);
500-
return GIT_EOWNER;
536+
error = GIT_EOWNER;
537+
538+
done:
539+
git_config_free(config);
540+
git_str_dispose(&data.tmp);
541+
return error;
501542
}
502543

503544
static int find_repo(

tests/libgit2/repo/open.c

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,26 @@
33
#include "sysdir.h"
44
#include <ctype.h>
55

6+
static git_buf config_path = GIT_BUF_INIT;
7+
8+
void test_repo_open__initialize(void)
9+
{
10+
cl_git_pass(git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &config_path));
11+
}
612

713
void test_repo_open__cleanup(void)
814
{
915
cl_git_sandbox_cleanup();
1016
cl_fixture_cleanup("empty_standard_repo");
17+
cl_fixture_cleanup("__global_config");
1118

1219
if (git_fs_path_isdir("alternate"))
1320
git_futils_rmdir_r("alternate", NULL, GIT_RMDIR_REMOVE_FILES);
1421

1522
git_fs_path__set_owner(GIT_FS_PATH_MOCK_OWNER_NONE);
23+
24+
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, config_path.ptr));
25+
git_buf_dispose(&config_path);
1626
}
1727

1828
void test_repo_open__bare_empty_repo(void)
@@ -480,11 +490,103 @@ void test_repo_open__validates_dir_ownership(void)
480490
void test_repo_open__can_allowlist_dirs_with_problematic_ownership(void)
481491
{
482492
git_repository *repo;
493+
git_str config_path = GIT_STR_INIT,
494+
config_filename = GIT_STR_INIT,
495+
config_data = GIT_STR_INIT;
483496

484497
cl_fixture_sandbox("empty_standard_repo");
485498
cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git"));
486499

487500
git_fs_path__set_owner(GIT_FS_PATH_MOCK_OWNER_OTHER);
488501
cl_git_fail(git_repository_open(&repo, "empty_standard_repo"));
489502

503+
/* Add safe.directory options to the global configuration */
504+
git_str_joinpath(&config_path, clar_sandbox_path(), "__global_config");
505+
cl_must_pass(p_mkdir(config_path.ptr, 0777));
506+
git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, config_path.ptr);
507+
508+
git_str_joinpath(&config_filename, config_path.ptr, ".gitconfig");
509+
510+
git_str_printf(&config_data,
511+
"[foo]\n" \
512+
"\tbar = Foobar\n" \
513+
"\tbaz = Baz!\n" \
514+
"[safe]\n" \
515+
"\tdirectory = /non/existent/path\n" \
516+
"\tdirectory = /\n" \
517+
"\tdirectory = c:\\\\temp\n" \
518+
"\tdirectory = %s/%s\n" \
519+
"\tdirectory = /tmp\n" \
520+
"[bar]\n" \
521+
"\tfoo = barfoo\n",
522+
clar_sandbox_path(), "empty_standard_repo");
523+
cl_git_rewritefile(config_filename.ptr, config_data.ptr);
524+
525+
cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
526+
git_repository_free(repo);
527+
528+
git_str_dispose(&config_path);
529+
git_str_dispose(&config_filename);
530+
git_str_dispose(&config_data);
531+
}
532+
533+
void test_repo_open__can_reset_safe_directory_list(void)
534+
{
535+
git_repository *repo;
536+
git_str config_path = GIT_STR_INIT,
537+
config_filename = GIT_STR_INIT,
538+
config_data = GIT_STR_INIT;
539+
540+
cl_fixture_sandbox("empty_standard_repo");
541+
cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git"));
542+
543+
git_fs_path__set_owner(GIT_FS_PATH_MOCK_OWNER_OTHER);
544+
cl_git_fail(git_repository_open(&repo, "empty_standard_repo"));
545+
546+
/* Add safe.directory options to the global configuration */
547+
git_str_joinpath(&config_path, clar_sandbox_path(), "__global_config");
548+
cl_must_pass(p_mkdir(config_path.ptr, 0777));
549+
git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, config_path.ptr);
550+
551+
git_str_joinpath(&config_filename, config_path.ptr, ".gitconfig");
552+
553+
/* The blank resets our sandbox directory and opening fails */
554+
555+
git_str_printf(&config_data,
556+
"[foo]\n" \
557+
"\tbar = Foobar\n" \
558+
"\tbaz = Baz!\n" \
559+
"[safe]\n" \
560+
"\tdirectory = %s/%s\n" \
561+
"\tdirectory = \n" \
562+
"\tdirectory = /tmp\n" \
563+
"[bar]\n" \
564+
"\tfoo = barfoo\n",
565+
clar_sandbox_path(), "empty_standard_repo");
566+
cl_git_rewritefile(config_filename.ptr, config_data.ptr);
567+
568+
cl_git_fail(git_repository_open(&repo, "empty_standard_repo"));
569+
570+
/* The blank resets tmp and allows subsequent declarations to succeed */
571+
572+
git_str_clear(&config_data);
573+
git_str_printf(&config_data,
574+
"[foo]\n" \
575+
"\tbar = Foobar\n" \
576+
"\tbaz = Baz!\n" \
577+
"[safe]\n" \
578+
"\tdirectory = /tmp\n" \
579+
"\tdirectory = \n" \
580+
"\tdirectory = %s/%s\n" \
581+
"[bar]\n" \
582+
"\tfoo = barfoo\n",
583+
clar_sandbox_path(), "empty_standard_repo");
584+
cl_git_rewritefile(config_filename.ptr, config_data.ptr);
585+
586+
cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
587+
git_repository_free(repo);
588+
589+
git_str_dispose(&config_path);
590+
git_str_dispose(&config_filename);
591+
git_str_dispose(&config_data);
490592
}

0 commit comments

Comments
 (0)