Skip to content

Commit a9eac6a

Browse files
authored
Merge pull request libgit2#6268 from libgit2/ethomson/ownership_13
Validate repository directory ownership (v1.3)
2 parents 37caa8d + b58e905 commit a9eac6a

File tree

20 files changed

+605
-152
lines changed

20 files changed

+605
-152
lines changed

include/git2/common.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,9 @@ typedef enum {
211211
GIT_OPT_SET_ODB_PACKED_PRIORITY,
212212
GIT_OPT_SET_ODB_LOOSE_PRIORITY,
213213
GIT_OPT_GET_EXTENSIONS,
214-
GIT_OPT_SET_EXTENSIONS
214+
GIT_OPT_SET_EXTENSIONS,
215+
GIT_OPT_GET_OWNER_VALIDATION,
216+
GIT_OPT_SET_OWNER_VALIDATION
215217
} git_libgit2_opt_t;
216218

217219
/**
@@ -449,6 +451,14 @@ typedef enum {
449451
* > to support repositories with the `noop` extension but does want
450452
* > to support repositories with the `newext` extension.
451453
*
454+
* opts(GIT_OPT_GET_OWNER_VALIDATION, int *enabled)
455+
* > Gets the owner validation setting for repository
456+
* > directories.
457+
*
458+
* opts(GIT_OPT_SET_OWNER_VALIDATION, int enabled)
459+
* > Set that repository directories should be owned by the current
460+
* > user. The default is to validate ownership.
461+
*
452462
* @param option Option key
453463
* @param ... value to set the option
454464
* @return 0 on success, <0 on failure

include/git2/errors.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ typedef enum {
5858
GIT_EMISMATCH = -33, /**< Hashsum mismatch in object */
5959
GIT_EINDEXDIRTY = -34, /**< Unsaved changes in the index would be overwritten */
6060
GIT_EAPPLYFAIL = -35, /**< Patch application failed */
61+
GIT_EOWNER = -36 /**< The object is not owned by the current user */
6162
} git_error_code;
6263

6364
/**

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/libgit2.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,14 @@ int git_libgit2_opts(int key, ...)
390390
}
391391
break;
392392

393+
case GIT_OPT_GET_OWNER_VALIDATION:
394+
*(va_arg(ap, int *)) = git_repository__validate_ownership;
395+
break;
396+
397+
case GIT_OPT_SET_OWNER_VALIDATION:
398+
git_repository__validate_ownership = (va_arg(ap, int) != 0);
399+
break;
400+
393401
default:
394402
git_error_set(GIT_ERROR_INVALID, "invalid option key");
395403
error = -1;

src/path.c

Lines changed: 213 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2024,78 +2024,237 @@ 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+
static git_path__mock_owner_t mock_owner = GIT_PATH_MOCK_OWNER_NONE;
2028+
2029+
void git_path__set_owner(git_path__mock_owner_t owner)
2030+
{
2031+
mock_owner = owner;
2032+
}
2033+
2034+
#ifdef GIT_WIN32
2035+
static PSID *sid_dup(PSID sid)
2036+
{
2037+
DWORD len;
2038+
PSID dup;
2039+
2040+
len = GetLengthSid(sid);
2041+
2042+
if ((dup = git__malloc(len)) == NULL)
2043+
return NULL;
2044+
2045+
if (!CopySid(len, dup, sid)) {
2046+
git_error_set(GIT_ERROR_OS, "could not duplicate sid");
2047+
git__free(dup);
2048+
return NULL;
2049+
}
2050+
2051+
return dup;
2052+
}
2053+
2054+
static int current_user_sid(PSID *out)
20282055
{
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;
20372056
TOKEN_USER *info = NULL;
2038-
DWORD err, len;
2039-
int ret;
2057+
HANDLE token = NULL;
2058+
DWORD len = 0;
2059+
int error = -1;
20402060

2041-
if (git_win32_path_from_utf8(buf, path) < 0)
2042-
return -1;
2061+
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
2062+
git_error_set(GIT_ERROR_OS, "could not lookup process information");
2063+
goto done;
2064+
}
2065+
2066+
if (GetTokenInformation(token, TokenUser, NULL, 0, &len) ||
2067+
GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
2068+
git_error_set(GIT_ERROR_OS, "could not lookup token metadata");
2069+
goto done;
2070+
}
20432071

2044-
err = GetNamedSecurityInfoW(buf, SE_FILE_OBJECT,
2045-
OWNER_SECURITY_INFORMATION |
2046-
DACL_SECURITY_INFORMATION,
2047-
&owner_sid, NULL, NULL, NULL, &descriptor);
2072+
info = git__malloc(len);
2073+
GIT_ERROR_CHECK_ALLOC(info);
20482074

2049-
if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
2050-
ret = GIT_ENOTFOUND;
2051-
goto cleanup;
2075+
if (!GetTokenInformation(token, TokenUser, info, len, &len)) {
2076+
git_error_set(GIT_ERROR_OS, "could not lookup current user");
2077+
goto done;
20522078
}
20532079

2054-
if (err != ERROR_SUCCESS) {
2080+
if ((*out = sid_dup(info->User.Sid)))
2081+
error = 0;
2082+
2083+
done:
2084+
if (token)
2085+
CloseHandle(token);
2086+
2087+
git__free(info);
2088+
return error;
2089+
}
2090+
2091+
static int file_owner_sid(PSID *out, const char *path)
2092+
{
2093+
git_win32_path path_w32;
2094+
PSECURITY_DESCRIPTOR descriptor = NULL;
2095+
PSID owner_sid;
2096+
DWORD ret;
2097+
int error = -1;
2098+
2099+
if (git_win32_path_from_utf8(path_w32, path) < 0)
2100+
return -1;
2101+
2102+
ret = GetNamedSecurityInfoW(path_w32, SE_FILE_OBJECT,
2103+
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
2104+
&owner_sid, NULL, NULL, NULL, &descriptor);
2105+
2106+
if (ret == ERROR_FILE_NOT_FOUND || ret == ERROR_PATH_NOT_FOUND)
2107+
error = GIT_ENOTFOUND;
2108+
else if (ret != ERROR_SUCCESS)
20552109
git_error_set(GIT_ERROR_OS, "failed to get security information");
2056-
ret = GIT_ERROR;
2057-
goto cleanup;
2110+
else if (!IsValidSid(owner_sid))
2111+
git_error_set(GIT_ERROR_OS, "file owner is not valid");
2112+
else if ((*out = sid_dup(owner_sid)))
2113+
error = 0;
2114+
2115+
if (descriptor)
2116+
LocalFree(descriptor);
2117+
2118+
return error;
2119+
}
2120+
2121+
int git_path_owner_is_current_user(bool *out, const char *path)
2122+
{
2123+
PSID owner_sid = NULL, user_sid = NULL;
2124+
int error = -1;
2125+
2126+
if (mock_owner) {
2127+
*out = (mock_owner == GIT_PATH_MOCK_OWNER_CURRENT_USER);
2128+
return 0;
2129+
}
2130+
2131+
if ((error = file_owner_sid(&owner_sid, path)) < 0 ||
2132+
(error = current_user_sid(&user_sid)) < 0)
2133+
goto done;
2134+
2135+
*out = EqualSid(owner_sid, user_sid);
2136+
error = 0;
2137+
2138+
done:
2139+
git__free(owner_sid);
2140+
git__free(user_sid);
2141+
return error;
2142+
}
2143+
2144+
int git_path_owner_is_system(bool *out, const char *path)
2145+
{
2146+
PSID owner_sid;
2147+
2148+
if (mock_owner) {
2149+
*out = (mock_owner == GIT_PATH_MOCK_OWNER_SYSTEM);
2150+
return 0;
20582151
}
20592152

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;
2153+
if (file_owner_sid(&owner_sid, path) < 0)
2154+
return -1;
2155+
2156+
*out = IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) ||
2157+
IsWellKnownSid(owner_sid, WinLocalSystemSid);
2158+
2159+
git__free(owner_sid);
2160+
return 0;
2161+
}
2162+
2163+
int git_path_owner_is_system_or_current_user(bool *out, const char *path)
2164+
{
2165+
PSID owner_sid = NULL, user_sid = NULL;
2166+
int error = -1;
2167+
2168+
if (mock_owner) {
2169+
*out = (mock_owner == GIT_PATH_MOCK_OWNER_SYSTEM ||
2170+
mock_owner == GIT_PATH_MOCK_OWNER_CURRENT_USER);
2171+
return 0;
20642172
}
20652173

2174+
if (file_owner_sid(&owner_sid, path) < 0)
2175+
goto done;
2176+
20662177
if (IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) ||
20672178
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;
2179+
*out = 1;
2180+
error = 0;
2181+
goto done;
2182+
}
2183+
2184+
if (current_user_sid(&user_sid) < 0)
2185+
goto done;
2186+
2187+
*out = EqualSid(owner_sid, user_sid);
2188+
error = 0;
2189+
2190+
done:
2191+
git__free(owner_sid);
2192+
git__free(user_sid);
2193+
return error;
2194+
}
2195+
2196+
#else
2197+
2198+
static int path_owner_is(bool *out, const char *path, uid_t *uids, size_t uids_len)
2199+
{
2200+
struct stat st;
2201+
size_t i;
2202+
2203+
*out = false;
2204+
2205+
if (p_lstat(path, &st) != 0) {
2206+
if (errno == ENOENT)
2207+
return GIT_ENOTFOUND;
2208+
2209+
git_error_set(GIT_ERROR_OS, "could not stat '%s'", path);
2210+
return -1;
2211+
}
2212+
2213+
for (i = 0; i < uids_len; i++) {
2214+
if (uids[i] == st.st_uid) {
2215+
*out = true;
2216+
break;
20802217
}
20812218
}
20822219

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;
2220+
return 0;
2221+
}
2222+
2223+
int git_path_owner_is_current_user(bool *out, const char *path)
2224+
{
2225+
uid_t userid = geteuid();
2226+
2227+
if (mock_owner) {
2228+
*out = (mock_owner == GIT_PATH_MOCK_OWNER_CURRENT_USER);
2229+
return 0;
20922230
}
2093-
git__free(info);
20942231

2095-
cleanup:
2096-
if (descriptor)
2097-
LocalFree(descriptor);
2232+
return path_owner_is(out, path, &userid, 1);
2233+
}
20982234

2099-
return ret;
2100-
#endif
2235+
int git_path_owner_is_system(bool *out, const char *path)
2236+
{
2237+
uid_t userid = 0;
2238+
2239+
if (mock_owner) {
2240+
*out = (mock_owner == GIT_PATH_MOCK_OWNER_SYSTEM);
2241+
return 0;
2242+
}
2243+
2244+
return path_owner_is(out, path, &userid, 1);
2245+
}
2246+
2247+
int git_path_owner_is_system_or_current_user(bool *out, const char *path)
2248+
{
2249+
uid_t userids[2] = { geteuid(), 0 };
2250+
2251+
if (mock_owner) {
2252+
*out = (mock_owner == GIT_PATH_MOCK_OWNER_SYSTEM ||
2253+
mock_owner == GIT_PATH_MOCK_OWNER_CURRENT_USER);
2254+
return 0;
2255+
}
2256+
2257+
return path_owner_is(out, path, userids, 2);
21012258
}
2259+
2260+
#endif

0 commit comments

Comments
 (0)