Skip to content

Commit

Permalink
Merge pull request #6268 from libgit2/ethomson/ownership_13
Browse files Browse the repository at this point in the history
Validate repository directory ownership (v1.3)
  • Loading branch information
ethomson committed Apr 12, 2022
2 parents 37caa8d + b58e905 commit a9eac6a
Show file tree
Hide file tree
Showing 20 changed files with 605 additions and 152 deletions.
12 changes: 11 additions & 1 deletion include/git2/common.h
Expand Up @@ -211,7 +211,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;

/**
Expand Down Expand Up @@ -449,6 +451,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
Expand Down
1 change: 1 addition & 0 deletions include/git2/errors.h
Expand Up @@ -58,6 +58,7 @@ typedef enum {
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_EOWNER = -36 /**< The object is not owned by the current user */
} git_error_code;

/**
Expand Down
16 changes: 10 additions & 6 deletions src/config.c
Expand Up @@ -1118,16 +1118,20 @@ int git_config_find_system(git_buf *path)
int git_config_find_programdata(git_buf *path)
{
int ret;
bool is_safe;

if ((ret = git_buf_sanitize(path)) < 0)
if ((ret = git_buf_sanitize(path)) < 0 ||
(ret = git_sysdir_find_programdata_file(path,
GIT_CONFIG_FILENAME_PROGRAMDATA)) < 0 ||
(ret = git_path_owner_is_system_or_current_user(&is_safe, path->ptr)) < 0)
return ret;

ret = git_sysdir_find_programdata_file(path,
GIT_CONFIG_FILENAME_PROGRAMDATA);
if (ret != GIT_OK)
return ret;
if (!is_safe) {
git_error_set(GIT_ERROR_CONFIG, "programdata path has invalid ownership");
return -1;
}

return git_path_validate_system_file_ownership(path->ptr);
return 0;
}

int git_config__global_location(git_buf *buf)
Expand Down
8 changes: 8 additions & 0 deletions src/libgit2.c
Expand Up @@ -390,6 +390,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;
Expand Down
267 changes: 213 additions & 54 deletions src/path.c
Expand Up @@ -2024,78 +2024,237 @@ bool git_path_supports_symlinks(const char *dir)
return supported;
}

int git_path_validate_system_file_ownership(const char *path)
static git_path__mock_owner_t mock_owner = GIT_PATH_MOCK_OWNER_NONE;

void git_path__set_owner(git_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_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_PATH_MOCK_OWNER_CURRENT_USER);
return 0;
}

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_path_owner_is_system(bool *out, const char *path)
{
PSID owner_sid;

if (mock_owner) {
*out = (mock_owner == GIT_PATH_MOCK_OWNER_SYSTEM);
return 0;
}

if (!IsValidSid(owner_sid)) {
git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is unknown");
ret = GIT_ERROR;
goto cleanup;
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_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_PATH_MOCK_OWNER_SYSTEM ||
mock_owner == GIT_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 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_path_owner_is_current_user(bool *out, const char *path)
{
uid_t userid = geteuid();

if (mock_owner) {
*out = (mock_owner == GIT_PATH_MOCK_OWNER_CURRENT_USER);
return 0;
}
git__free(info);

cleanup:
if (descriptor)
LocalFree(descriptor);
return path_owner_is(out, path, &userid, 1);
}

return ret;
#endif
int git_path_owner_is_system(bool *out, const char *path)
{
uid_t userid = 0;

if (mock_owner) {
*out = (mock_owner == GIT_PATH_MOCK_OWNER_SYSTEM);
return 0;
}

return path_owner_is(out, path, &userid, 1);
}

int git_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_PATH_MOCK_OWNER_SYSTEM ||
mock_owner == GIT_PATH_MOCK_OWNER_CURRENT_USER);
return 0;
}

return path_owner_is(out, path, userids, 2);
}

#endif

0 comments on commit a9eac6a

Please sign in to comment.