Skip to content

Commit

Permalink
mingw: special-case administrators even more (#712)
Browse files Browse the repository at this point in the history
The check for dubious ownership has one particular quirk on Windows: if
running as an administrator, files owned by the Administrators _group_
are considered owned by the user.

The rationale for that is: When running in elevated mode, Git creates
files that aren't owned by the individual user but by the Administrators
group.

There is yet another quirk, though: The check I introduced to determine
whether the current user is an administrator uses the
`CheckTokenMembership()` function with the current process token. And
that check only succeeds when running in elevated mode!

Let's be a bit more lenient here and look harder whether the current
user is an administrator. We do this by looking for a so-called "linked
token". That token exists when administrators run in non-elevated mode,
and can be used to create a new process in elevated mode. And feeding
_that_ token to the `CheckTokenMembership()` function succeeds!
  • Loading branch information
dscho committed Jan 1, 2025
2 parents 344af5c + eb1cff5 commit 2c95417
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 11 deletions.
39 changes: 28 additions & 11 deletions compat/mingw.c
Original file line number Diff line number Diff line change
Expand Up @@ -3718,31 +3718,44 @@ static void setup_windows_environment(void)
has_symlinks = 0;
}

static PSID get_current_user_sid(void)
static void get_current_user_sid(PSID *sid, HANDLE *linked_token)
{
HANDLE token;
DWORD len = 0;
PSID result = NULL;
TOKEN_ELEVATION_TYPE elevationType;
DWORD size;

*sid = NULL;
*linked_token = NULL;

if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
return NULL;
return;

if (!GetTokenInformation(token, TokenUser, NULL, 0, &len)) {
TOKEN_USER *info = xmalloc((size_t)len);
if (GetTokenInformation(token, TokenUser, info, len, &len)) {
len = GetLengthSid(info->User.Sid);
result = xmalloc(len);
if (!CopySid(len, result, info->User.Sid)) {
*sid = xmalloc(len);
if (!CopySid(len, *sid, info->User.Sid)) {
error(_("failed to copy SID (%ld)"),
GetLastError());
FREE_AND_NULL(result);
FREE_AND_NULL(*sid);
}
}
FREE_AND_NULL(info);
}
CloseHandle(token);

return result;
if (GetTokenInformation(token, TokenElevationType, &elevationType, sizeof(elevationType), &size) &&
elevationType == TokenElevationTypeLimited) {
/*
* The current process is run by a member of the Administrators
* group, but is not running elevated.
*/
if (!GetTokenInformation(token, TokenLinkedToken, linked_token, sizeof(*linked_token), &size))
linked_token = NULL; /* there is no linked token */
}

CloseHandle(token);
}

static BOOL user_sid_to_user_name(PSID sid, LPSTR *str)
Expand Down Expand Up @@ -3821,18 +3834,22 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)
if (err == ERROR_SUCCESS && sid && IsValidSid(sid)) {
/* Now, verify that the SID matches the current user's */
static PSID current_user_sid;
static HANDLE linked_token;
BOOL is_member;

if (!current_user_sid)
current_user_sid = get_current_user_sid();
get_current_user_sid(&current_user_sid, &linked_token);

if (current_user_sid &&
IsValidSid(current_user_sid) &&
EqualSid(sid, current_user_sid))
result = 1;
else if (IsWellKnownSid(sid, WinBuiltinAdministratorsSid) &&
CheckTokenMembership(NULL, sid, &is_member) &&
is_member)
((CheckTokenMembership(NULL, sid, &is_member) &&
is_member) ||
(linked_token &&
CheckTokenMembership(linked_token, sid, &is_member) &&
is_member)))
/*
* If owned by the Administrators group, and the
* current user is an administrator, we consider that
Expand Down
19 changes: 19 additions & 0 deletions t/helper/test-path-utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,25 @@ int cmd__path_utils(int argc, const char **argv)
return !!res;
}

if (argc > 1 && !strcmp(argv[1], "is_path_owned_by_current_user")) {
int res = 0;

for (int i = 2; i < argc; i++) {
struct strbuf buf = STRBUF_INIT;

if (is_path_owned_by_current_user(argv[i], &buf))
printf("'%s' is owned by current SID\n", argv[i]);
else {
printf("'%s' is not owned by current SID: %s\n", argv[i], buf.buf);
res = 1;
}

strbuf_release(&buf);
}

return res;
}

fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
argv[1] ? argv[1] : "(there was none)");
return 1;
Expand Down

0 comments on commit 2c95417

Please sign in to comment.