Skip to content

Commit bf2620b

Browse files
committed
fs_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 7e8d9be commit bf2620b

File tree

4 files changed

+220
-68
lines changed

4 files changed

+220
-68
lines changed

src/libgit2/config.c

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,14 +1170,18 @@ int git_config_find_programdata(git_buf *path)
11701170

11711171
int git_config__find_programdata(git_str *path)
11721172
{
1173-
int ret;
1173+
bool is_safe;
11741174

1175-
ret = git_sysdir_find_programdata_file(path, GIT_CONFIG_FILENAME_PROGRAMDATA);
1175+
if (git_sysdir_find_programdata_file(path, GIT_CONFIG_FILENAME_PROGRAMDATA) < 0 ||
1176+
git_fs_path_owner_is_system_or_current_user(&is_safe, path->ptr) < 0)
1177+
return -1;
11761178

1177-
if (ret != GIT_OK)
1178-
return ret;
1179+
if (!is_safe) {
1180+
git_error_set(GIT_ERROR_CONFIG, "programdata path has invalid ownership");
1181+
return -1;
1182+
}
11791183

1180-
return git_fs_path_validate_system_file_ownership(path->ptr);
1184+
return 0;
11811185
}
11821186

11831187
int git_config__global_location(git_str *buf)

src/util/fs_path.c

Lines changed: 172 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1785,82 +1785,199 @@ bool git_fs_path_supports_symlinks(const char *dir)
17851785
return supported;
17861786
}
17871787

1788-
int git_fs_path_validate_system_file_ownership(const char *path)
1788+
#ifdef GIT_WIN32
1789+
static PSID *sid_dup(PSID sid)
17891790
{
1790-
#ifndef GIT_WIN32
1791-
GIT_UNUSED(path);
1792-
return GIT_OK;
1793-
#else
1794-
git_win32_path buf;
1795-
PSID owner_sid;
1796-
PSECURITY_DESCRIPTOR descriptor = NULL;
1797-
HANDLE token;
1798-
TOKEN_USER *info = NULL;
1799-
DWORD err, len;
1800-
int ret;
1791+
DWORD len;
1792+
PSID dup;
18011793

1802-
if (git_win32_path_from_utf8(buf, path) < 0)
1803-
return -1;
1794+
len = GetLengthSid(sid);
18041795

1805-
err = GetNamedSecurityInfoW(buf, SE_FILE_OBJECT,
1806-
OWNER_SECURITY_INFORMATION |
1807-
DACL_SECURITY_INFORMATION,
1808-
&owner_sid, NULL, NULL, NULL, &descriptor);
1796+
if ((dup = git__malloc(len)) == NULL)
1797+
return NULL;
18091798

1810-
if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
1811-
ret = GIT_ENOTFOUND;
1812-
goto cleanup;
1799+
if (!CopySid(len, dup, sid)) {
1800+
git_error_set(GIT_ERROR_OS, "could not duplicate sid");
1801+
git__free(dup);
1802+
return NULL;
18131803
}
18141804

1815-
if (err != ERROR_SUCCESS) {
1816-
git_error_set(GIT_ERROR_OS, "failed to get security information");
1817-
ret = GIT_ERROR;
1818-
goto cleanup;
1805+
return dup;
1806+
}
1807+
1808+
static int current_user_sid(PSID *out)
1809+
{
1810+
TOKEN_USER *info = NULL;
1811+
HANDLE token = NULL;
1812+
DWORD len = 0;
1813+
int error = -1;
1814+
1815+
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
1816+
git_error_set(GIT_ERROR_OS, "could not lookup process information");
1817+
goto done;
18191818
}
18201819

1821-
if (!IsValidSid(owner_sid)) {
1822-
git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is unknown");
1823-
ret = GIT_ERROR;
1824-
goto cleanup;
1820+
if (GetTokenInformation(token, TokenUser, NULL, 0, &len) ||
1821+
GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
1822+
git_error_set(GIT_ERROR_OS, "could not lookup token metadata");
1823+
goto done;
18251824
}
18261825

1826+
info = git__malloc(len);
1827+
GIT_ERROR_CHECK_ALLOC(info);
1828+
1829+
if (!GetTokenInformation(token, TokenUser, info, len, &len)) {
1830+
git_error_set(GIT_ERROR_OS, "could not lookup current user");
1831+
goto done;
1832+
}
1833+
1834+
if ((*out = sid_dup(info->User.Sid)))
1835+
error = 0;
1836+
1837+
done:
1838+
if (token)
1839+
CloseHandle(token);
1840+
1841+
git__free(info);
1842+
return error;
1843+
}
1844+
1845+
static int file_owner_sid(PSID *out, const char *path)
1846+
{
1847+
git_win32_path path_w32;
1848+
PSECURITY_DESCRIPTOR descriptor = NULL;
1849+
PSID owner_sid;
1850+
DWORD ret;
1851+
int error = -1;
1852+
1853+
if (git_win32_path_from_utf8(path_w32, path) < 0)
1854+
return -1;
1855+
1856+
ret = GetNamedSecurityInfoW(path_w32, SE_FILE_OBJECT,
1857+
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
1858+
&owner_sid, NULL, NULL, NULL, &descriptor);
1859+
1860+
if (ret == ERROR_FILE_NOT_FOUND || ret == ERROR_PATH_NOT_FOUND)
1861+
error = GIT_ENOTFOUND;
1862+
else if (ret != ERROR_SUCCESS)
1863+
git_error_set(GIT_ERROR_OS, "failed to get security information");
1864+
else if (!IsValidSid(owner_sid))
1865+
git_error_set(GIT_ERROR_OS, "file owner is not valid");
1866+
else if ((*out = sid_dup(owner_sid)))
1867+
error = 0;
1868+
1869+
if (descriptor)
1870+
LocalFree(descriptor);
1871+
1872+
return error;
1873+
}
1874+
1875+
int git_fs_path_owner_is_current_user(bool *out, const char *path)
1876+
{
1877+
PSID owner_sid = NULL, user_sid = NULL;
1878+
int error = -1;
1879+
1880+
if ((error = file_owner_sid(&owner_sid, path)) < 0 ||
1881+
(error = current_user_sid(&user_sid)) < 0)
1882+
goto done;
1883+
1884+
*out = EqualSid(owner_sid, user_sid);
1885+
error = 0;
1886+
1887+
done:
1888+
git__free(owner_sid);
1889+
git__free(user_sid);
1890+
return error;
1891+
}
1892+
1893+
int git_fs_path_owner_is_system(bool *out, const char *path)
1894+
{
1895+
PSID owner_sid;
1896+
1897+
if (file_owner_sid(&owner_sid, path) < 0)
1898+
return -1;
1899+
1900+
*out = IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) ||
1901+
IsWellKnownSid(owner_sid, WinLocalSystemSid);
1902+
1903+
git__free(owner_sid);
1904+
return 0;
1905+
}
1906+
1907+
int git_fs_path_owner_is_system_or_current_user(bool *out, const char *path)
1908+
{
1909+
PSID owner_sid = NULL, user_sid = NULL;
1910+
int error = -1;
1911+
1912+
if (file_owner_sid(&owner_sid, path) < 0)
1913+
goto done;
1914+
18271915
if (IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) ||
18281916
IsWellKnownSid(owner_sid, WinLocalSystemSid)) {
1829-
ret = GIT_OK;
1830-
goto cleanup;
1917+
*out = 1;
1918+
error = 0;
1919+
goto done;
18311920
}
18321921

1833-
/* Obtain current user's SID */
1834-
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token) &&
1835-
!GetTokenInformation(token, TokenUser, NULL, 0, &len)) {
1836-
info = git__malloc(len);
1837-
GIT_ERROR_CHECK_ALLOC(info);
1838-
if (!GetTokenInformation(token, TokenUser, info, len, &len)) {
1839-
git__free(info);
1840-
info = NULL;
1841-
}
1922+
if (current_user_sid(&user_sid) < 0)
1923+
goto done;
1924+
1925+
*out = EqualSid(owner_sid, user_sid);
1926+
error = 0;
1927+
1928+
done:
1929+
git__free(owner_sid);
1930+
git__free(user_sid);
1931+
return error;
1932+
}
1933+
1934+
#else
1935+
1936+
static int fs_path_owner_is(bool *out, const char *path, uid_t *uids, size_t uids_len)
1937+
{
1938+
struct stat st;
1939+
size_t i;
1940+
1941+
*out = false;
1942+
1943+
if (p_lstat(path, &st) != 0) {
1944+
if (errno == ENOENT)
1945+
return GIT_ENOTFOUND;
1946+
1947+
git_error_set(GIT_ERROR_OS, "could not stat '%s'", path);
1948+
return -1;
18421949
}
18431950

1844-
/*
1845-
* If the file is owned by the same account that is running the current
1846-
* process, it's okay to read from that file.
1847-
*/
1848-
if (info && EqualSid(owner_sid, info->User.Sid))
1849-
ret = GIT_OK;
1850-
else {
1851-
git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is not valid");
1852-
ret = GIT_ERROR;
1951+
for (i = 0; i < uids_len; i++) {
1952+
if (uids[i] == st.st_uid) {
1953+
*out = true;
1954+
break;
1955+
}
18531956
}
1854-
git__free(info);
18551957

1856-
cleanup:
1857-
if (descriptor)
1858-
LocalFree(descriptor);
1958+
return 0;
1959+
}
18591960

1860-
return ret;
1861-
#endif
1961+
int git_fs_path_owner_is_current_user(bool *out, const char *path)
1962+
{
1963+
uid_t userid = geteuid();
1964+
return fs_path_owner_is(out, path, &userid, 1);
18621965
}
18631966

1967+
int git_fs_path_owner_is_system(bool *out, const char *path)
1968+
{
1969+
uid_t userid = 0;
1970+
return fs_path_owner_is(out, path, &userid, 1);
1971+
}
1972+
1973+
int git_fs_path_owner_is_system_or_current_user(bool *out, const char *path)
1974+
{
1975+
uid_t userids[2] = { geteuid(), 0 };
1976+
return fs_path_owner_is(out, path, userids, 2);
1977+
}
1978+
1979+
#endif
1980+
18641981
int git_fs_path_find_executable(git_str *fullpath, const char *executable)
18651982
{
18661983
#ifdef GIT_WIN32

src/util/fs_path.h

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -732,16 +732,22 @@ int git_fs_path_normalize_slashes(git_str *out, const char *path);
732732
bool git_fs_path_supports_symlinks(const char *dir);
733733

734734
/**
735-
* Validate a system file's ownership
736-
*
737735
* Verify that the file in question is owned by an administrator or system
738-
* account, or at least by the current user.
739-
*
740-
* This function returns 0 if successful. If the file is not owned by any of
741-
* these, or any other if there have been problems determining the file
742-
* ownership, it returns -1.
736+
* account.
737+
*/
738+
int git_fs_path_owner_is_system(bool *out, const char *path);
739+
740+
/**
741+
* Verify that the file in question is owned by the current user;
742+
*/
743+
744+
int git_fs_path_owner_is_current_user(bool *out, const char *path);
745+
746+
/**
747+
* Verify that the file in question is owned by an administrator or system
748+
* account _or_ the current user;
743749
*/
744-
int git_fs_path_validate_system_file_ownership(const char *path);
750+
int git_fs_path_owner_is_system_or_current_user(bool *out, const char *path);
745751

746752
/**
747753
* Search the current PATH for the given executable, returning the full

tests/util/path.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,3 +737,28 @@ void test_path__find_exe_in_path(void)
737737
git_str_dispose(&sandbox_path);
738738
git__free(orig_path);
739739
}
740+
741+
void test_path__validate_current_user_ownership(void)
742+
{
743+
bool is_cur;
744+
745+
cl_must_pass(p_mkdir("testdir", 0777));
746+
cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "testdir"));
747+
cl_assert_equal_i(is_cur, 1);
748+
749+
cl_git_rewritefile("testfile", "This is a test file.");
750+
cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "testfile"));
751+
cl_assert_equal_i(is_cur, 1);
752+
753+
#ifdef GIT_WIN32
754+
cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "C:\\"));
755+
cl_assert_equal_i(is_cur, 0);
756+
757+
cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "c:\\path\\does\\not\\exist"));
758+
#else
759+
cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "/"));
760+
cl_assert_equal_i(is_cur, 0);
761+
762+
cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "/path/does/not/exist"));
763+
#endif
764+
}

0 commit comments

Comments
 (0)