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 <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Schindelin committed Mar 25, 2025 at 10:38 UTC 03a4e46d122d5f24b6e1cd872eb996851c1563da
1 file changed +28 -11
compat/mingw.c
+28 -11
@@ -2826,31 +2826,44 @@ static void setup_windows_environment(void)
2826 }
2827 }
2828
2829 -static PSID get_current_user_sid(void)
2829 +static void get_current_user_sid(PSID *sid, HANDLE *linked_token)
2830 {
2831 HANDLE token;
2832 DWORD len = 0;
2833 - PSID result = NULL;
2833 + TOKEN_ELEVATION_TYPE elevationType;
2834 + DWORD size;
2835 +
2836 + *sid = NULL;
2837 + *linked_token = NULL;
2838
2839 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
2836 - return NULL;
2840 + return;
2841
2842 if (!GetTokenInformation(token, TokenUser, NULL, 0, &len)) {
2843 TOKEN_USER *info = xmalloc((size_t)len);
2844 if (GetTokenInformation(token, TokenUser, info, len, &len)) {
2845 len = GetLengthSid(info->User.Sid);
2842 - result = xmalloc(len);
2843 - if (!CopySid(len, result, info->User.Sid)) {
2846 + *sid = xmalloc(len);
2847 + if (!CopySid(len, *sid, info->User.Sid)) {
2848 error(_("failed to copy SID (%ld)"),
2849 GetLastError());
2846 - FREE_AND_NULL(result);
2850 + FREE_AND_NULL(*sid);
2851 }
2852 }
2853 FREE_AND_NULL(info);
2854 }
2851 - CloseHandle(token);
2855
2853 - return result;
2856 + if (GetTokenInformation(token, TokenElevationType, &elevationType, sizeof(elevationType), &size) &&
2857 + elevationType == TokenElevationTypeLimited) {
2858 + /*
2859 + * The current process is run by a member of the Administrators
2860 + * group, but is not running elevated.
2861 + */
2862 + if (!GetTokenInformation(token, TokenLinkedToken, linked_token, sizeof(*linked_token), &size))
2863 + linked_token = NULL; /* there is no linked token */
2864 + }
2865 +
2866 + CloseHandle(token);
2867 }
2868
2869 static BOOL user_sid_to_user_name(PSID sid, LPSTR *str)
@@ -2931,18 +2944,22 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)
2944 else if (sid && IsValidSid(sid)) {
2945 /* Now, verify that the SID matches the current user's */
2946 static PSID current_user_sid;
2947 + static HANDLE linked_token;
2948 BOOL is_member;
2949
2950 if (!current_user_sid)
2937 - current_user_sid = get_current_user_sid();
2951 + get_current_user_sid(&current_user_sid, &linked_token);
2952
2953 if (current_user_sid &&
2954 IsValidSid(current_user_sid) &&
2955 EqualSid(sid, current_user_sid))
2956 result = 1;
2957 else if (IsWellKnownSid(sid, WinBuiltinAdministratorsSid) &&
2944 - CheckTokenMembership(NULL, sid, &is_member) &&
2945 - is_member)
2958 + ((CheckTokenMembership(NULL, sid, &is_member) &&
2959 + is_member) ||
2960 + (linked_token &&
2961 + CheckTokenMembership(linked_token, sid, &is_member) &&
2962 + is_member)))
2963 /*
2964 * If owned by the Administrators group, and the
2965 * current user is an administrator, we consider that