diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index c08801e11..e45809263 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -582,16 +582,27 @@ static int validate_ownership_cb(const git_config_entry *entry, void *payload) } else if (strcmp(entry->value, "*") == 0) { *data->is_safe = true; } else { + bool is_prefix = false, match; + if (git_str_sets(&data->tmp, entry->value) < 0) return -1; + /* + * A value ending with slash* is treated as a prefix match. + * Strip only the '*', leaving the trailing slash in place. + */ + if (git__suffixcmp(data->tmp.ptr, "/*") == 0) { + is_prefix = true; + git_str_shorten(&data->tmp, 1); + } + if (!git_fs_path_is_root(data->tmp.ptr)) { /* Input must not have trailing backslash. */ if (!data->tmp.size || - data->tmp.ptr[data->tmp.size - 1] == '/') + (!is_prefix && data->tmp.ptr[data->tmp.size - 1] == '/')) return 0; - if (git_fs_path_to_dir(&data->tmp) < 0) + if (!is_prefix && git_fs_path_to_dir(&data->tmp) < 0) return -1; } @@ -623,7 +634,11 @@ static int validate_ownership_cb(const git_config_entry *entry, void *payload) if (strncmp(test_path, "%(prefix)//", strlen("%(prefix)//")) == 0) test_path += strlen("%(prefix)/"); - if (strcmp(test_path, data->repo_path) == 0) + match = is_prefix ? + (git__prefixcmp(data->repo_path, test_path) == 0) : + (strcmp(test_path, data->repo_path) == 0); + + if (match) *data->is_safe = true; } diff --git a/tests/libgit2/repo/open.c b/tests/libgit2/repo/open.c index 52b14ed5f..c2edb3bc0 100644 --- a/tests/libgit2/repo/open.c +++ b/tests/libgit2/repo/open.c @@ -659,6 +659,26 @@ void test_repo_open__can_wildcard_allowlist_with_problematic_ownership(void) cl_git_pass(test_safe_path("*")); } +void test_repo_open__can_allowlist_dirs_wildcard(void) +{ + git_str path = GIT_STR_INIT; + + cl_git_pass(git_str_printf( + &path, "%s/*", clar_sandbox_path())); + cl_git_pass(test_safe_path(path.ptr)); + git_str_dispose(&path); +} + +void test_repo_open__allowlist_dirs_cannot_have_wildcard_suffix(void) +{ + git_str path = GIT_STR_INIT; + + cl_git_pass(git_str_printf( + &path, "%s/%s*", clar_sandbox_path(), "empty_standard_repo")); + cl_git_fail_with(GIT_EOWNER, test_safe_path(path.ptr)); + git_str_dispose(&path); +} + void test_repo_open__can_allowlist_bare_gitdir(void) { git_str path = GIT_STR_INIT; @@ -669,6 +689,38 @@ void test_repo_open__can_allowlist_bare_gitdir(void) git_str_dispose(&path); } +void test_repo_open__can_allowlist_bare_wildcard_gitdir(void) +{ + git_str path = GIT_STR_INIT; + + cl_git_pass(git_str_printf( + &path, "%s/*", clar_sandbox_path())); + cl_git_pass(test_bare_safe_path(path.ptr)); + + git_str_dispose(&path); +} + +void test_repo_open__allowlist_bare_dirs_cannot_have_wildcard_suffix(void) +{ + git_str path = GIT_STR_INIT; + + cl_git_pass(git_str_printf( + &path, "%s/%s*", clar_sandbox_path(), "testrepo.git")); + cl_git_fail_with(GIT_EOWNER, test_bare_safe_path(path.ptr)); + + git_str_dispose(&path); +} + +void test_repo_open__allowlist_relative_bare_dirs_cannot_have_wildcard_suffix(void) +{ + git_str path = GIT_STR_INIT; + + cl_git_pass(git_str_printf(&path, "%s*", clar_sandbox_path())); + cl_git_fail_with(GIT_EOWNER, test_bare_safe_path(path.ptr)); + + git_str_dispose(&path); +} + void test_repo_open__can_wildcard_allowlist_bare_gitdir(void) { cl_git_pass(test_bare_safe_path("*")); @@ -691,6 +743,23 @@ void test_repo_open__can_handle_prefixed_safe_paths(void) #endif } +void test_repo_open__can_handle_prefixed_wildcard_safe_paths(void) +{ +#ifndef GIT_WIN32 + git_str path = GIT_STR_INIT; + + /* + * Using "%(prefix)/" becomes "%(prefix)//tmp/foo" - so + * "%(prefix)/" is stripped and means the literal path + * follows. + */ + cl_git_pass(git_str_printf(&path, "%%(prefix)/%s/*", + clar_sandbox_path())); + cl_git_pass(test_safe_path(path.ptr)); + git_str_dispose(&path); +#endif +} + void test_repo_open__prefixed_safe_paths_must_have_two_slashes(void) { git_str path = GIT_STR_INIT;