summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2022-04-12 14:11:54 -0400
committerGitHub <noreply@github.com>2022-04-12 14:11:54 -0400
commit63970244fb2d49794e7b5d268a2defe4299fd3ad (patch)
treee392dbbef54ade88914f7bf1550d2cf8340704a6
parent7e8d9be0cc168c22cbaf1116bd5b27c0a6076565 (diff)
parent4161ebdd50dc29205dcc94cba5320c6d0d2537a7 (diff)
downloadlibgit2-63970244fb2d49794e7b5d268a2defe4299fd3ad.tar.gz
Merge pull request #6266 from libgit2/ethomson/ownership
Validate repository directory ownership
-rw-r--r--include/git2/common.h12
-rw-r--r--include/git2/errors.h3
-rw-r--r--src/libgit2/config.c14
-rw-r--r--src/libgit2/libgit2.c8
-rw-r--r--src/libgit2/repository.c125
-rw-r--r--src/libgit2/repository.h1
-rw-r--r--src/util/fs_path.c267
-rw-r--r--src/util/fs_path.h36
-rw-r--r--tests/clar/clar_libgit2.c5
-rw-r--r--tests/clar/clar_libgit2.h1
-rw-r--r--tests/clar/main.c1
-rw-r--r--tests/libgit2/repo/config.c1
-rw-r--r--tests/libgit2/repo/open.c147
-rw-r--r--tests/util/path.c25
14 files changed, 554 insertions, 92 deletions
diff --git a/include/git2/common.h b/include/git2/common.h
index 52000e8eb..c3e3e7b4e 100644
--- a/include/git2/common.h
+++ b/include/git2/common.h
@@ -225,7 +225,9 @@ typedef enum {
GIT_OPT_SET_ODB_PACKED_PRIORITY,
GIT_OPT_SET_ODB_LOOSE_PRIORITY,
GIT_OPT_GET_EXTENSIONS,
- GIT_OPT_SET_EXTENSIONS
+ GIT_OPT_SET_EXTENSIONS,
+ GIT_OPT_GET_OWNER_VALIDATION,
+ GIT_OPT_SET_OWNER_VALIDATION
} git_libgit2_opt_t;
/**
@@ -463,6 +465,14 @@ typedef enum {
* > to support repositories with the `noop` extension but does want
* > to support repositories with the `newext` extension.
*
+ * opts(GIT_OPT_GET_OWNER_VALIDATION, int *enabled)
+ * > Gets the owner validation setting for repository
+ * > directories.
+ *
+ * opts(GIT_OPT_SET_OWNER_VALIDATION, int enabled)
+ * > Set that repository directories should be owned by the current
+ * > user. The default is to validate ownership.
+ *
* @param option Option key
* @param ... value to set the option
* @return 0 on success, <0 on failure
diff --git a/include/git2/errors.h b/include/git2/errors.h
index aba6d75e3..a61964bbb 100644
--- a/include/git2/errors.h
+++ b/include/git2/errors.h
@@ -57,7 +57,8 @@ typedef enum {
GIT_RETRY = -32, /**< Internal only */
GIT_EMISMATCH = -33, /**< Hashsum mismatch in object */
GIT_EINDEXDIRTY = -34, /**< Unsaved changes in the index would be overwritten */
- GIT_EAPPLYFAIL = -35 /**< Patch application failed */
+ GIT_EAPPLYFAIL = -35, /**< Patch application failed */
+ GIT_EOWNER = -36 /**< The object is not owned by the current user */
} git_error_code;
/**
diff --git a/src/libgit2/config.c b/src/libgit2/config.c
index 88da34c5e..6bd59f2a5 100644
--- a/src/libgit2/config.c
+++ b/src/libgit2/config.c
@@ -1170,14 +1170,18 @@ int git_config_find_programdata(git_buf *path)
int git_config__find_programdata(git_str *path)
{
- int ret;
+ bool is_safe;
- ret = git_sysdir_find_programdata_file(path, GIT_CONFIG_FILENAME_PROGRAMDATA);
+ if (git_sysdir_find_programdata_file(path, GIT_CONFIG_FILENAME_PROGRAMDATA) < 0 ||
+ git_fs_path_owner_is_system_or_current_user(&is_safe, path->ptr) < 0)
+ return -1;
- if (ret != GIT_OK)
- return ret;
+ if (!is_safe) {
+ git_error_set(GIT_ERROR_CONFIG, "programdata path has invalid ownership");
+ return -1;
+ }
- return git_fs_path_validate_system_file_ownership(path->ptr);
+ return 0;
}
int git_config__global_location(git_str *buf)
diff --git a/src/libgit2/libgit2.c b/src/libgit2/libgit2.c
index efad3bf6d..2fda0722e 100644
--- a/src/libgit2/libgit2.c
+++ b/src/libgit2/libgit2.c
@@ -406,6 +406,14 @@ int git_libgit2_opts(int key, ...)
}
break;
+ case GIT_OPT_GET_OWNER_VALIDATION:
+ *(va_arg(ap, int *)) = git_repository__validate_ownership;
+ break;
+
+ case GIT_OPT_SET_OWNER_VALIDATION:
+ git_repository__validate_ownership = (va_arg(ap, int) != 0);
+ break;
+
default:
git_error_set(GIT_ERROR_INVALID, "invalid option key");
error = -1;
diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c
index f202623d6..48a0b70f5 100644
--- a/src/libgit2/repository.c
+++ b/src/libgit2/repository.c
@@ -39,6 +39,7 @@
# include "win32/w32_util.h"
#endif
+bool git_repository__validate_ownership = true;
bool git_repository__fsync_gitdir = false;
static const struct {
@@ -65,6 +66,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,6 +485,63 @@ 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) {
+ if (error == GIT_ENOTFOUND)
+ error = 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);
+ error = GIT_EOWNER;
+
+done:
+ git_config_free(config);
+ git_str_dispose(&data.tmp);
+ return error;
+}
+
static int find_repo(
git_str *gitdir_path,
git_str *workdir_path,
@@ -856,6 +915,7 @@ int git_repository_open_ext(
gitlink = GIT_STR_INIT, commondir = GIT_STR_INIT;
git_repository *repo = NULL;
git_config *config = NULL;
+ const char *validation_path;
int version = 0;
if (flags & GIT_REPOSITORY_OPEN_FROM_ENV)
@@ -904,16 +964,24 @@ int git_repository_open_ext(
if ((error = check_extensions(config, version)) < 0)
goto cleanup;
- if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0)
+ if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0) {
repo->is_bare = 1;
- else {
-
+ } else {
if (config &&
((error = load_config_data(repo, config)) < 0 ||
(error = load_workdir(repo, config, &workdir)) < 0))
goto cleanup;
}
+ /*
+ * Ensure that the git directory is owned by the current user.
+ */
+ validation_path = repo->is_bare ? repo->gitdir : repo->workdir;
+
+ if (git_repository__validate_ownership &&
+ (error = validate_ownership(validation_path)) < 0)
+ goto cleanup;
+
cleanup:
git_str_dispose(&gitdir);
git_str_dispose(&workdir);
@@ -1607,13 +1675,40 @@ static bool is_filesystem_case_insensitive(const char *gitdir_path)
return is_insensitive;
}
-static bool are_symlinks_supported(const char *wd_path)
+/*
+ * Return a configuration object with only the global and system
+ * configurations; no repository-level configuration.
+ */
+static int load_global_config(git_config **config)
{
- git_config *config = NULL;
git_str global_buf = GIT_STR_INIT;
git_str xdg_buf = GIT_STR_INIT;
git_str system_buf = GIT_STR_INIT;
git_str programdata_buf = GIT_STR_INIT;
+ int error;
+
+ git_config__find_global(&global_buf);
+ git_config__find_xdg(&xdg_buf);
+ git_config__find_system(&system_buf);
+ git_config__find_programdata(&programdata_buf);
+
+ error = load_config(config, NULL,
+ path_unless_empty(&global_buf),
+ path_unless_empty(&xdg_buf),
+ path_unless_empty(&system_buf),
+ path_unless_empty(&programdata_buf));
+
+ git_str_dispose(&global_buf);
+ git_str_dispose(&xdg_buf);
+ git_str_dispose(&system_buf);
+ git_str_dispose(&programdata_buf);
+
+ return error;
+}
+
+static bool are_symlinks_supported(const char *wd_path)
+{
+ git_config *config = NULL;
int symlinks = 0;
/*
@@ -1624,19 +1719,9 @@ static bool are_symlinks_supported(const char *wd_path)
* _not_ set, then we do not test or enable symlink support.
*/
#ifdef GIT_WIN32
- git_config__find_global(&global_buf);
- git_config__find_xdg(&xdg_buf);
- git_config__find_system(&system_buf);
- git_config__find_programdata(&programdata_buf);
-
- if (load_config(&config, NULL,
- path_unless_empty(&global_buf),
- path_unless_empty(&xdg_buf),
- path_unless_empty(&system_buf),
- path_unless_empty(&programdata_buf)) < 0)
- goto done;
-
- if (git_config_get_bool(&symlinks, config, "core.symlinks") < 0 || !symlinks)
+ if (load_global_config(&config) < 0 ||
+ git_config_get_bool(&symlinks, config, "core.symlinks") < 0 ||
+ !symlinks)
goto done;
#endif
@@ -1644,10 +1729,6 @@ static bool are_symlinks_supported(const char *wd_path)
goto done;
done:
- git_str_dispose(&global_buf);
- git_str_dispose(&xdg_buf);
- git_str_dispose(&system_buf);
- git_str_dispose(&programdata_buf);
git_config_free(config);
return symlinks != 0;
}
diff --git a/src/libgit2/repository.h b/src/libgit2/repository.h
index 3c3aa1e8e..a488f2bf2 100644
--- a/src/libgit2/repository.h
+++ b/src/libgit2/repository.h
@@ -34,6 +34,7 @@
#define GIT_DIR_SHORTNAME "GIT~1"
extern bool git_repository__fsync_gitdir;
+extern bool git_repository__validate_ownership;
/** Cvar cache identifiers */
typedef enum {
diff --git a/src/util/fs_path.c b/src/util/fs_path.c
index 65842426c..9bd773d27 100644
--- a/src/util/fs_path.c
+++ b/src/util/fs_path.c
@@ -1785,82 +1785,241 @@ done:
return supported;
}
-int git_fs_path_validate_system_file_ownership(const char *path)
+static git_fs_path__mock_owner_t mock_owner = GIT_FS_PATH_MOCK_OWNER_NONE;
+
+void git_fs_path__set_owner(git_fs_path__mock_owner_t owner)
+{
+ mock_owner = owner;
+}
+
+#ifdef GIT_WIN32
+static PSID *sid_dup(PSID sid)
+{
+ DWORD len;
+ PSID dup;
+
+ len = GetLengthSid(sid);
+
+ if ((dup = git__malloc(len)) == NULL)
+ return NULL;
+
+ if (!CopySid(len, dup, sid)) {
+ git_error_set(GIT_ERROR_OS, "could not duplicate sid");
+ git__free(dup);
+ return NULL;
+ }
+
+ return dup;
+}
+
+static int current_user_sid(PSID *out)
{
-#ifndef GIT_WIN32
- GIT_UNUSED(path);
- return GIT_OK;
-#else
- git_win32_path buf;
- PSID owner_sid;
- PSECURITY_DESCRIPTOR descriptor = NULL;
- HANDLE token;
TOKEN_USER *info = NULL;
- DWORD err, len;
- int ret;
+ HANDLE token = NULL;
+ DWORD len = 0;
+ int error = -1;
- if (git_win32_path_from_utf8(buf, path) < 0)
- return -1;
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
+ git_error_set(GIT_ERROR_OS, "could not lookup process information");
+ goto done;
+ }
+
+ if (GetTokenInformation(token, TokenUser, NULL, 0, &len) ||
+ GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ git_error_set(GIT_ERROR_OS, "could not lookup token metadata");
+ goto done;
+ }
- err = GetNamedSecurityInfoW(buf, SE_FILE_OBJECT,
- OWNER_SECURITY_INFORMATION |
- DACL_SECURITY_INFORMATION,
- &owner_sid, NULL, NULL, NULL, &descriptor);
+ info = git__malloc(len);
+ GIT_ERROR_CHECK_ALLOC(info);
- if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
- ret = GIT_ENOTFOUND;
- goto cleanup;
+ if (!GetTokenInformation(token, TokenUser, info, len, &len)) {
+ git_error_set(GIT_ERROR_OS, "could not lookup current user");
+ goto done;
}
- if (err != ERROR_SUCCESS) {
+ if ((*out = sid_dup(info->User.Sid)))
+ error = 0;
+
+done:
+ if (token)
+ CloseHandle(token);
+
+ git__free(info);
+ return error;
+}
+
+static int file_owner_sid(PSID *out, const char *path)
+{
+ git_win32_path path_w32;
+ PSECURITY_DESCRIPTOR descriptor = NULL;
+ PSID owner_sid;
+ DWORD ret;
+ int error = -1;
+
+ if (git_win32_path_from_utf8(path_w32, path) < 0)
+ return -1;
+
+ ret = GetNamedSecurityInfoW(path_w32, SE_FILE_OBJECT,
+ OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
+ &owner_sid, NULL, NULL, NULL, &descriptor);
+
+ if (ret == ERROR_FILE_NOT_FOUND || ret == ERROR_PATH_NOT_FOUND)
+ error = GIT_ENOTFOUND;
+ else if (ret != ERROR_SUCCESS)
git_error_set(GIT_ERROR_OS, "failed to get security information");
- ret = GIT_ERROR;
- goto cleanup;
+ else if (!IsValidSid(owner_sid))
+ git_error_set(GIT_ERROR_OS, "file owner is not valid");
+ else if ((*out = sid_dup(owner_sid)))
+ error = 0;
+
+ if (descriptor)
+ LocalFree(descriptor);
+
+ return error;
+}
+
+int git_fs_path_owner_is_current_user(bool *out, const char *path)
+{
+ PSID owner_sid = NULL, user_sid = NULL;
+ int error = -1;
+
+ if (mock_owner) {
+ *out = (mock_owner == GIT_FS_PATH_MOCK_OWNER_CURRENT_USER);
+ return 0;
}
- if (!IsValidSid(owner_sid)) {
- git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is unknown");
- ret = GIT_ERROR;
- goto cleanup;
+ if ((error = file_owner_sid(&owner_sid, path)) < 0 ||
+ (error = current_user_sid(&user_sid)) < 0)
+ goto done;
+
+ *out = EqualSid(owner_sid, user_sid);
+ error = 0;
+
+done:
+ git__free(owner_sid);
+ git__free(user_sid);
+ return error;
+}
+
+int git_fs_path_owner_is_system(bool *out, const char *path)
+{
+ PSID owner_sid;
+
+ if (mock_owner) {
+ *out = (mock_owner == GIT_FS_PATH_MOCK_OWNER_SYSTEM);
+ return 0;
}
+ if (file_owner_sid(&owner_sid, path) < 0)
+ return -1;
+
+ *out = IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) ||
+ IsWellKnownSid(owner_sid, WinLocalSystemSid);
+
+ git__free(owner_sid);
+ return 0;
+}
+
+int git_fs_path_owner_is_system_or_current_user(bool *out, const char *path)
+{
+ PSID owner_sid = NULL, user_sid = NULL;
+ int error = -1;
+
+ if (mock_owner) {
+ *out = (mock_owner == GIT_FS_PATH_MOCK_OWNER_SYSTEM ||
+ mock_owner == GIT_FS_PATH_MOCK_OWNER_CURRENT_USER);
+ return 0;
+ }
+
+ if (file_owner_sid(&owner_sid, path) < 0)
+ goto done;
+
if (IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) ||
IsWellKnownSid(owner_sid, WinLocalSystemSid)) {
- ret = GIT_OK;
- goto cleanup;
- }
-
- /* Obtain current user's SID */
- if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token) &&
- !GetTokenInformation(token, TokenUser, NULL, 0, &len)) {
- info = git__malloc(len);
- GIT_ERROR_CHECK_ALLOC(info);
- if (!GetTokenInformation(token, TokenUser, info, len, &len)) {
- git__free(info);
- info = NULL;
+ *out = 1;
+ error = 0;
+ goto done;
+ }
+
+ if (current_user_sid(&user_sid) < 0)
+ goto done;
+
+ *out = EqualSid(owner_sid, user_sid);
+ error = 0;
+
+done:
+ git__free(owner_sid);
+ git__free(user_sid);
+ return error;
+}
+
+#else
+
+static int fs_path_owner_is(bool *out, const char *path, uid_t *uids, size_t uids_len)
+{
+ struct stat st;
+ size_t i;
+
+ *out = false;
+
+ if (p_lstat(path, &st) != 0) {
+ if (errno == ENOENT)
+ return GIT_ENOTFOUND;
+
+ git_error_set(GIT_ERROR_OS, "could not stat '%s'", path);
+ return -1;
+ }
+
+ for (i = 0; i < uids_len; i++) {
+ if (uids[i] == st.st_uid) {
+ *out = true;
+ break;
}
}
- /*
- * If the file is owned by the same account that is running the current
- * process, it's okay to read from that file.
- */
- if (info && EqualSid(owner_sid, info->User.Sid))
- ret = GIT_OK;
- else {
- git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is not valid");
- ret = GIT_ERROR;
+ return 0;
+}
+
+int git_fs_path_owner_is_current_user(bool *out, const char *path)
+{
+ uid_t userid = geteuid();
+
+ if (mock_owner) {
+ *out = (mock_owner == GIT_FS_PATH_MOCK_OWNER_CURRENT_USER);
+ return 0;
}
- git__free(info);
-cleanup:
- if (descriptor)
- LocalFree(descriptor);
+ return fs_path_owner_is(out, path, &userid, 1);
+}
- return ret;
-#endif
+int git_fs_path_owner_is_system(bool *out, const char *path)
+{
+ uid_t userid = 0;
+
+ if (mock_owner) {
+ *out = (mock_owner == GIT_FS_PATH_MOCK_OWNER_SYSTEM);
+ return 0;
+ }
+
+ return fs_path_owner_is(out, path, &userid, 1);
}
+int git_fs_path_owner_is_system_or_current_user(bool *out, const char *path)
+{
+ uid_t userids[2] = { geteuid(), 0 };
+
+ if (mock_owner) {
+ *out = (mock_owner == GIT_FS_PATH_MOCK_OWNER_SYSTEM ||
+ mock_owner == GIT_FS_PATH_MOCK_OWNER_CURRENT_USER);
+ return 0;
+ }
+
+ return fs_path_owner_is(out, path, userids, 2);
+}
+
+#endif
+
int git_fs_path_find_executable(git_str *fullpath, const char *executable)
{
#ifdef GIT_WIN32
diff --git a/src/util/fs_path.h b/src/util/fs_path.h
index bb840c43c..cf4664178 100644
--- a/src/util/fs_path.h
+++ b/src/util/fs_path.h
@@ -731,17 +731,37 @@ int git_fs_path_normalize_slashes(git_str *out, const char *path);
bool git_fs_path_supports_symlinks(const char *dir);
+typedef enum {
+ GIT_FS_PATH_MOCK_OWNER_NONE = 0, /* do filesystem lookups as normal */
+ GIT_FS_PATH_MOCK_OWNER_SYSTEM = 1,
+ GIT_FS_PATH_MOCK_OWNER_CURRENT_USER = 2,
+ GIT_FS_PATH_MOCK_OWNER_OTHER = 3
+} git_fs_path__mock_owner_t;
+
+/**
+ * Sets the mock ownership for files; subsequent calls to
+ * `git_fs_path_owner_is_*` functions will return this data until cleared
+ * with `GIT_FS_PATH_MOCK_OWNER_NONE`.
+ */
+void git_fs_path__set_owner(git_fs_path__mock_owner_t owner);
+
/**
- * Validate a system file's ownership
- *
* Verify that the file in question is owned by an administrator or system
- * account, or at least by the current user.
- *
- * This function returns 0 if successful. If the file is not owned by any of
- * these, or any other if there have been problems determining the file
- * ownership, it returns -1.
+ * account.
+ */
+int git_fs_path_owner_is_system(bool *out, const char *path);
+
+/**
+ * Verify that the file in question is owned by the current user;
+ */
+
+int git_fs_path_owner_is_current_user(bool *out, const char *path);
+
+/**
+ * Verify that the file in question is owned by an administrator or system
+ * account _or_ the current user;
*/
-int git_fs_path_validate_system_file_ownership(const char *path);
+int git_fs_path_owner_is_system_or_current_user(bool *out, const char *path);
/**
* Search the current PATH for the given executable, returning the full
diff --git a/tests/clar/clar_libgit2.c b/tests/clar/clar_libgit2.c
index 55a09d111..783b457f9 100644
--- a/tests/clar/clar_libgit2.c
+++ b/tests/clar/clar_libgit2.c
@@ -599,6 +599,11 @@ void cl_sandbox_set_search_path_defaults(void)
git_str_dispose(&path);
}
+void cl_sandbox_disable_ownership_validation(void)
+{
+ git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0);
+}
+
#ifdef GIT_WIN32
bool cl_sandbox_supports_8dot3(void)
{
diff --git a/tests/clar/clar_libgit2.h b/tests/clar/clar_libgit2.h
index e3b7bd9f8..da3f41524 100644
--- a/tests/clar/clar_libgit2.h
+++ b/tests/clar/clar_libgit2.h
@@ -222,6 +222,7 @@ void cl_fake_home(void);
void cl_fake_home_cleanup(void *);
void cl_sandbox_set_search_path_defaults(void);
+void cl_sandbox_disable_ownership_validation(void);
#ifdef GIT_WIN32
# define cl_msleep(x) Sleep(x)
diff --git a/tests/clar/main.c b/tests/clar/main.c
index 56751c288..d879073a8 100644
--- a/tests/clar/main.c
+++ b/tests/clar/main.c
@@ -26,6 +26,7 @@ int main(int argc, char *argv[])
cl_global_trace_register();
cl_sandbox_set_search_path_defaults();
+ cl_sandbox_disable_ownership_validation();
/* Run the test suite */
res = clar_test_run();
diff --git a/tests/libgit2/repo/config.c b/tests/libgit2/repo/config.c
index ee7e43dff..37f6b521d 100644
--- a/tests/libgit2/repo/config.c
+++ b/tests/libgit2/repo/config.c
@@ -28,7 +28,6 @@ void test_repo_config__cleanup(void)
cl_assert(!git_fs_path_isdir("alternate"));
cl_fixture_cleanup("empty_standard_repo");
-
}
void test_repo_config__can_open_global_when_there_is_no_file(void)
diff --git a/tests/libgit2/repo/open.c b/tests/libgit2/repo/open.c
index f7ed2c373..4b6609a81 100644
--- a/tests/libgit2/repo/open.c
+++ b/tests/libgit2/repo/open.c
@@ -3,13 +3,30 @@
#include "sysdir.h"
#include <ctype.h>
+static int validate_ownership = 0;
+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));
+ cl_git_pass(git_libgit2_opts(GIT_OPT_GET_OWNER_VALIDATION, &validate_ownership));
+}
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);
+
+ cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, validate_ownership));
}
void test_repo_open__bare_empty_repo(void)
@@ -453,3 +470,133 @@ void test_repo_open__force_bare(void)
git_repository_free(barerepo);
}
+void test_repo_open__validates_dir_ownership(void)
+{
+ git_repository *repo;
+
+ cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1));
+
+ cl_fixture_sandbox("empty_standard_repo");
+ cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git"));
+
+ /* When the current user owns the repo config, that's acceptable */
+ git_fs_path__set_owner(GIT_FS_PATH_MOCK_OWNER_CURRENT_USER);
+ cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+ git_repository_free(repo);
+
+ /* When the system user owns the repo config, fail */
+ git_fs_path__set_owner(GIT_FS_PATH_MOCK_OWNER_SYSTEM);
+ cl_git_fail(git_repository_open(&repo, "empty_standard_repo"));
+
+ /* When an unknown user owns the repo config, fail */
+ git_fs_path__set_owner(GIT_FS_PATH_MOCK_OWNER_OTHER);
+ cl_git_fail(git_repository_open(&repo, "empty_standard_repo"));
+}
+
+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_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1));
+
+ 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");
+
+ 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_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1));
+
+ 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);
+}
diff --git a/tests/util/path.c b/tests/util/path.c
index 404c17a2b..2c39e0887 100644
--- a/tests/util/path.c
+++ b/tests/util/path.c
@@ -737,3 +737,28 @@ void test_path__find_exe_in_path(void)
git_str_dispose(&sandbox_path);
git__free(orig_path);
}
+
+void test_path__validate_current_user_ownership(void)
+{
+ bool is_cur;
+
+ cl_must_pass(p_mkdir("testdir", 0777));
+ cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "testdir"));
+ cl_assert_equal_i(is_cur, 1);
+
+ cl_git_rewritefile("testfile", "This is a test file.");
+ cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "testfile"));
+ cl_assert_equal_i(is_cur, 1);
+
+#ifdef GIT_WIN32
+ cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "C:\\"));
+ cl_assert_equal_i(is_cur, 0);
+
+ cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "c:\\path\\does\\not\\exist"));
+#else
+ cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "/"));
+ cl_assert_equal_i(is_cur, 0);
+
+ cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "/path/does/not/exist"));
+#endif
+}