Skip to content

Commit 973d959

Browse files
committed
path: refactor ownership checks into current user and system
Provide individual file ownership checks for both the current user and the system user, as well as a combined current user and system user check.
1 parent 670415a commit 973d959

File tree

4 files changed

+223
-71
lines changed

4 files changed

+223
-71
lines changed

src/config.c

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,16 +1118,20 @@ int git_config_find_system(git_buf *path)
11181118
int git_config_find_programdata(git_buf *path)
11191119
{
11201120
int ret;
1121+
bool is_safe;
11211122

1122-
if ((ret = git_buf_sanitize(path)) < 0)
1123+
if ((ret = git_buf_sanitize(path)) < 0 ||
1124+
(ret = git_sysdir_find_programdata_file(path,
1125+
GIT_CONFIG_FILENAME_PROGRAMDATA)) < 0 ||
1126+
(ret = git_path_owner_is_system_or_current_user(&is_safe, path->ptr)) < 0)
11231127
return ret;
11241128

1125-
ret = git_sysdir_find_programdata_file(path,
1126-
GIT_CONFIG_FILENAME_PROGRAMDATA);
1127-
if (ret != GIT_OK)
1128-
return ret;
1129+
if (!is_safe) {
1130+
git_error_set(GIT_ERROR_CONFIG, "programdata path has invalid ownership");
1131+
return -1;
1132+
}
11291133

1130-
return git_path_validate_system_file_ownership(path->ptr);
1134+
return 0;
11311135
}
11321136

11331137
int git_config__global_location(git_buf *buf)

src/path.c

Lines changed: 174 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2024,78 +2024,195 @@ bool git_path_supports_symlinks(const char *dir)
20242024
return supported;
20252025
}
20262026

2027-
int git_path_validate_system_file_ownership(const char *path)
2027+
#ifdef GIT_WIN32
2028+
static PSID *sid_dup(PSID sid)
20282029
{
2029-
#ifndef GIT_WIN32
2030-
GIT_UNUSED(path);
2031-
return GIT_OK;
2032-
#else
2033-
git_win32_path buf;
2034-
PSID owner_sid;
2035-
PSECURITY_DESCRIPTOR descriptor = NULL;
2036-
HANDLE token;
2037-
TOKEN_USER *info = NULL;
2038-
DWORD err, len;
2039-
int ret;
2030+
DWORD len;
2031+
PSID dup;
20402032

2041-
if (git_win32_path_from_utf8(buf, path) < 0)
2042-
return -1;
2033+
len = GetLengthSid(sid);
20432034

2044-
err = GetNamedSecurityInfoW(buf, SE_FILE_OBJECT,
2045-
OWNER_SECURITY_INFORMATION |
2046-
DACL_SECURITY_INFORMATION,
2047-
&owner_sid, NULL, NULL, NULL, &descriptor);
2035+
if ((dup = git__malloc(len)) == NULL)
2036+
return NULL;
20482037

2049-
if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
2050-
ret = GIT_ENOTFOUND;
2051-
goto cleanup;
2038+
if (!CopySid(len, dup, sid)) {
2039+
git_error_set(GIT_ERROR_OS, "could not duplicate sid");
2040+
git__free(dup);
2041+
return NULL;
20522042
}
20532043

2054-
if (err != ERROR_SUCCESS) {
2055-
git_error_set(GIT_ERROR_OS, "failed to get security information");
2056-
ret = GIT_ERROR;
2057-
goto cleanup;
2058-
}
2044+
return dup;
2045+
}
2046+
2047+
static int current_user_sid(PSID *out)
2048+
{
2049+
TOKEN_USER *info = NULL;
2050+
HANDLE token = NULL;
2051+
DWORD len = 0;
2052+
int error = -1;
20592053

2060-
if (!IsValidSid(owner_sid)) {
2061-
git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is unknown");
2062-
ret = GIT_ERROR;
2063-
goto cleanup;
2054+
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
2055+
git_error_set(GIT_ERROR_OS, "could not lookup process information");
2056+
goto done;
20642057
}
20652058

2066-
if (IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) ||
2067-
IsWellKnownSid(owner_sid, WinLocalSystemSid)) {
2068-
ret = GIT_OK;
2069-
goto cleanup;
2070-
}
2071-
2072-
/* Obtain current user's SID */
2073-
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token) &&
2074-
!GetTokenInformation(token, TokenUser, NULL, 0, &len)) {
2075-
info = git__malloc(len);
2076-
GIT_ERROR_CHECK_ALLOC(info);
2077-
if (!GetTokenInformation(token, TokenUser, info, len, &len)) {
2078-
git__free(info);
2079-
info = NULL;
2080-
}
2059+
if (GetTokenInformation(token, TokenUser, NULL, 0, &len) ||
2060+
GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
2061+
git_error_set(GIT_ERROR_OS, "could not lookup token metadata");
2062+
goto done;
20812063
}
20822064

2083-
/*
2084-
* If the file is owned by the same account that is running the current
2085-
* process, it's okay to read from that file.
2086-
*/
2087-
if (info && EqualSid(owner_sid, info->User.Sid))
2088-
ret = GIT_OK;
2089-
else {
2090-
git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is not valid");
2091-
ret = GIT_ERROR;
2065+
info = git__malloc(len);
2066+
GIT_ERROR_CHECK_ALLOC(info);
2067+
2068+
if (!GetTokenInformation(token, TokenUser, info, len, &len)) {
2069+
git_error_set(GIT_ERROR_OS, "could not lookup current user");
2070+
goto done;
20922071
}
2072+
2073+
if ((*out = sid_dup(info->User.Sid)))
2074+
error = 0;
2075+
2076+
done:
2077+
if (token)
2078+
CloseHandle(token);
2079+
20932080
git__free(info);
2081+
return error;
2082+
}
2083+
2084+
static int file_owner_sid(PSID *out, const char *path)
2085+
{
2086+
git_win32_path path_w32;
2087+
PSECURITY_DESCRIPTOR descriptor = NULL;
2088+
PSID owner_sid;
2089+
DWORD ret;
2090+
int error = -1;
2091+
2092+
if (git_win32_path_from_utf8(path_w32, path) < 0)
2093+
return -1;
2094+
2095+
ret = GetNamedSecurityInfoW(path_w32, SE_FILE_OBJECT,
2096+
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
2097+
&owner_sid, NULL, NULL, NULL, &descriptor);
2098+
2099+
if (ret == ERROR_FILE_NOT_FOUND || ret == ERROR_PATH_NOT_FOUND)
2100+
error = GIT_ENOTFOUND;
2101+
else if (ret != ERROR_SUCCESS)
2102+
git_error_set(GIT_ERROR_OS, "failed to get security information");
2103+
else if (!IsValidSid(owner_sid))
2104+
git_error_set(GIT_ERROR_OS, "file owner is not valid");
2105+
else if ((*out = sid_dup(owner_sid)))
2106+
error = 0;
20942107

2095-
cleanup:
20962108
if (descriptor)
20972109
LocalFree(descriptor);
20982110

2099-
return ret;
2100-
#endif
2111+
return error;
21012112
}
2113+
2114+
int git_path_owner_is_current_user(bool *out, const char *path)
2115+
{
2116+
PSID owner_sid = NULL, user_sid = NULL;
2117+
int error = -1;
2118+
2119+
if ((error = file_owner_sid(&owner_sid, path)) < 0 ||
2120+
(error = current_user_sid(&user_sid)) < 0)
2121+
goto done;
2122+
2123+
*out = EqualSid(owner_sid, user_sid);
2124+
error = 0;
2125+
2126+
done:
2127+
git__free(owner_sid);
2128+
git__free(user_sid);
2129+
return error;
2130+
}
2131+
2132+
int git_path_owner_is_system(bool *out, const char *path)
2133+
{
2134+
PSID owner_sid;
2135+
2136+
if (file_owner_sid(&owner_sid, path) < 0)
2137+
return -1;
2138+
2139+
*out = IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) ||
2140+
IsWellKnownSid(owner_sid, WinLocalSystemSid);
2141+
2142+
git__free(owner_sid);
2143+
return 0;
2144+
}
2145+
2146+
int git_path_owner_is_system_or_current_user(bool *out, const char *path)
2147+
{
2148+
PSID owner_sid = NULL, user_sid = NULL;
2149+
int error = -1;
2150+
2151+
if (file_owner_sid(&owner_sid, path) < 0)
2152+
goto done;
2153+
2154+
if (IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) ||
2155+
IsWellKnownSid(owner_sid, WinLocalSystemSid)) {
2156+
*out = 1;
2157+
error = 0;
2158+
goto done;
2159+
}
2160+
2161+
if (current_user_sid(&user_sid) < 0)
2162+
goto done;
2163+
2164+
*out = EqualSid(owner_sid, user_sid);
2165+
error = 0;
2166+
2167+
done:
2168+
git__free(owner_sid);
2169+
git__free(user_sid);
2170+
return error;
2171+
}
2172+
2173+
#else
2174+
2175+
static int path_owner_is(bool *out, const char *path, uid_t *uids, size_t uids_len)
2176+
{
2177+
struct stat st;
2178+
size_t i;
2179+
2180+
*out = false;
2181+
2182+
if (p_lstat(path, &st) != 0) {
2183+
if (errno == ENOENT)
2184+
return GIT_ENOTFOUND;
2185+
2186+
git_error_set(GIT_ERROR_OS, "could not stat '%s'", path);
2187+
return -1;
2188+
}
2189+
2190+
for (i = 0; i < uids_len; i++) {
2191+
if (uids[i] == st.st_uid) {
2192+
*out = true;
2193+
break;
2194+
}
2195+
}
2196+
2197+
return 0;
2198+
}
2199+
2200+
int git_path_owner_is_current_user(bool *out, const char *path)
2201+
{
2202+
uid_t userid = geteuid();
2203+
return path_owner_is(out, path, &userid, 1);
2204+
}
2205+
2206+
int git_path_owner_is_system(bool *out, const char *path)
2207+
{
2208+
uid_t userid = 0;
2209+
return path_owner_is(out, path, &userid, 1);
2210+
}
2211+
2212+
int git_path_owner_is_system_or_current_user(bool *out, const char *path)
2213+
{
2214+
uid_t userids[2] = { geteuid(), 0 };
2215+
return path_owner_is(out, path, userids, 2);
2216+
}
2217+
2218+
#endif

src/path.h

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -723,15 +723,21 @@ int git_path_normalize_slashes(git_buf *out, const char *path);
723723
bool git_path_supports_symlinks(const char *dir);
724724

725725
/**
726-
* Validate a system file's ownership
727-
*
728726
* Verify that the file in question is owned by an administrator or system
729-
* account, or at least by the current user.
730-
*
731-
* This function returns 0 if successful. If the file is not owned by any of
732-
* these, or any other if there have been problems determining the file
733-
* ownership, it returns -1.
727+
* account.
728+
*/
729+
int git_path_owner_is_system(bool *out, const char *path);
730+
731+
/**
732+
* Verify that the file in question is owned by the current user;
733+
*/
734+
735+
int git_path_owner_is_current_user(bool *out, const char *path);
736+
737+
/**
738+
* Verify that the file in question is owned by an administrator or system
739+
* account _or_ the current user;
734740
*/
735-
int git_path_validate_system_file_ownership(const char *path);
741+
int git_path_owner_is_system_or_current_user(bool *out, const char *path);
736742

737743
#endif

tests/core/path.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,3 +659,28 @@ void test_core_path__git_path_is_file(void)
659659
cl_git_pass(git_path_is_gitfile("blob", 4, GIT_PATH_GITFILE_GITATTRIBUTES, GIT_PATH_FS_HFS));
660660
cl_git_fail(git_path_is_gitfile("blob", 4, 3, GIT_PATH_FS_HFS));
661661
}
662+
663+
void test_core_path__validate_current_user_ownership(void)
664+
{
665+
bool is_cur;
666+
667+
cl_must_pass(p_mkdir("testdir", 0777));
668+
cl_git_pass(git_path_owner_is_current_user(&is_cur, "testdir"));
669+
cl_assert_equal_i(is_cur, 1);
670+
671+
cl_git_rewritefile("testfile", "This is a test file.");
672+
cl_git_pass(git_path_owner_is_current_user(&is_cur, "testfile"));
673+
cl_assert_equal_i(is_cur, 1);
674+
675+
#ifdef GIT_WIN32
676+
cl_git_pass(git_path_owner_is_current_user(&is_cur, "C:\\"));
677+
cl_assert_equal_i(is_cur, 0);
678+
679+
cl_git_fail(git_path_owner_is_current_user(&is_cur, "c:\\path\\does\\not\\exist"));
680+
#else
681+
cl_git_pass(git_path_owner_is_current_user(&is_cur, "/"));
682+
cl_assert_equal_i(is_cur, 0);
683+
684+
cl_git_fail(git_path_owner_is_current_user(&is_cur, "/path/does/not/exist"));
685+
#endif
686+
}

0 commit comments

Comments
 (0)