From 7e6ed7f7bbc6fe84af5a46d653276d6a23534154 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 6 Dec 2024 19:50:12 +0100 Subject: [PATCH 1/2] mingw: special-case administrators even more 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! Signed-off-by: Johannes Schindelin --- compat/mingw.c | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index 8470936af099b7..1e46fa2ae8f57b 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -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) @@ -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(¤t_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 From eb1cff5283e4daf26ad587df038fd08c89fe4197 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 6 Dec 2024 20:12:33 +0100 Subject: [PATCH 2/2] test-tool path-utils: support debugging "dubious ownership" issues This adds a new sub-sub-command for `test-tool`, simply passing through the command-line arguments to the `is_path_owned_by_current_user()` function. Signed-off-by: Johannes Schindelin --- t/helper/test-path-utils.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index 72ac8d1b1b011d..f3c59e5028561c 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c @@ -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;