Fix clone ownership under Windows Administrator Protection#2039
Merged
Conversation
a251e89 to
8164d13
Compare
Under Windows "Administrator protection" (AP), an elevated process runs as a hidden, profile-separated shadow admin account (MACHINE\admin_<user>) whose SID is a local account (S-1-5-21-...) that differs from the real (non-elevated) user's SID. When `gvfs clone` runs elevated, the enlistment's `src` and `.git` directories are created owned by the Administrators group. EnsureDirectoryIsOwnedByCurrentUser then reassigned ownership to the current user to satisfy git's dubious-ownership check. Under AP the "current user" is the shadow admin, so ownership was set to admin_<user> — which the real user does not match. The real (non-elevated) user then hit `fatal: detected dubious ownership`, because git's Administrators-group membership grace does not cover another specific user's SID, and the shadow admin cannot SetOwner to the real user's SID either. Detect the AP shadow admin from the running process's own token — its effective elevation identity — and, when present, leave the directory owner as the Administrators group instead of reassigning it to the shadow admin. An Administrators-owned directory is accepted by modern git and the libgit2 non-elevated-admin-owner overlay for any Administrators member — the real user, the shadow admin, and SYSTEM (for automount) alike. Detection inspects the token rather than the TypeOfAdminApprovalMode policy value so it stays correct across an AP policy change that has not yet been rebooted (where the policy value is pending but elevation still yields a shadow admin). A shadow admin is identified as a local account SID (S-1-5-21-...) whose ProfileList profile directory is ADMIN_-prefixed; the real interactive user (a domain or Entra ID account) is excluded. Assisted-by: Claude Opus 4.8 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
8164d13 to
3cbc75f
Compare
KeithIsSleeping
approved these changes
Jul 6, 2026
tyrielv
added a commit
that referenced
this pull request
Jul 6, 2026
The `non-elevated-admin-owner.diff` overlay patch was copied verbatim from libgit2 PR #7200. That PR version had four bugs that maintainers fixed before merging to libgit2 `main`: - `linked_token` HANDLE from TokenLinkedToken was never CloseHandle'd, leaking a handle in the long-lived GVFS mount service (SYSTEM). - `current_user_sid` used `static HANDLE linked_token` — not thread-safe and retained a stale handle across calls; replaced with a per-call local initialized to NULL by the caller. - The TokenLinkedToken failure path nulled the local pointer parameter (`linked_token = NULL`) instead of `*linked_token = NULL` (+CloseHandle). - `current_user_sid()` was only invoked in the CURRENT_USER branch, but the admin-membership branch also reads `linked_token`; a caller passing only USER_IS_ADMINISTRATOR would read an uninitialized handle. It is now called unconditionally before either branch. Regenerate the patch so that, applied to the pinned libgit2 source, it produces the same corrected `src/util/fs_path.c` as upstream `main` (commits cc477ee + f9f36a6 + 44c05e5, merge e805a16, merged 2026-06-07). The patch is not yet in any libgit2 release, so the overlay remains necessary. Also bump the pinned libgit2 version 1.9.3 -> 1.9.4 (matching the official vcpkg port). fs_path.c is byte-identical between 1.9.3 and 1.9.4, so the patched region is unaffected and the diff applies cleanly to both. Verified: Build.bat Debug rebuilds libgit2 for both x64-windows-static-aot and x64-windows-dynamic triplets, applying non-elevated-admin-owner.diff with no hunk failures, and the full solution builds to an installer. Complements #2039 (the GVFS-side AP clone-ownership fix). Work item: AB#62918022. Assisted-by: Claude Opus 4.8 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Under Windows Administrator protection (AP), an elevated process runs as a hidden, profile-separated shadow admin account (
MACHINE\admin_<user>) whose SID is a local account (S-1-5-21-…) that differs from the real, non-elevated user's SID.When
gvfs cloneruns elevated, the enlistment'ssrcand.gitdirectories are created owned by the Administrators group.EnsureDirectoryIsOwnedByCurrentUserthen reassigned ownership to the current user to satisfy git's dubious-ownership check. Under AP the "current user" is the shadow admin, so ownership was set toadmin_<user>.The real (non-elevated) user then hits:
because git's Administrators-group-membership grace does not cover another specific user's SID, and the shadow admin cannot
SetOwnerto the real user's SID either.Fix
Detect the AP shadow admin from the running process's own token (its effective elevation identity) and, when present, leave the directory owner as the Administrators group instead of reassigning it to the shadow admin.
An
Administrators-owned directory is accepted by modern git and the libgit2non-elevated-admin-owneroverlay for any Administrators member — the real user, the shadow admin, andSYSTEM(for automount) alike. So skipping the reassignment for a shadow admin is correct for every consumer. When the current user is not a shadow admin, behavior is unchanged (owner reassigned to the current user, as before).Why token-based, not
TypeOfAdminApprovalModeAn earlier revision read the
TypeOfAdminApprovalModepolicy value. That reflects the pending policy, not the effective state: across an AP enable/disable that hasn't been rebooted, the policy value and the actual elevation behavior disagree, so the guard would misfire (e.g. policy reads1but elevation still yields a shadow admin → bug persists). Inspecting the process token is reboot-independent and targets the actual failure condition.A shadow admin is identified as a local account SID (
S-1-5-21-…,IsAccountSid()) whoseProfileListprofile directory isADMIN_-prefixed. The real interactive user (a domain / Entra ID account, e.g.S-1-12-1-…) is excluded.Why not also set
safe.directoryConsidered and deliberately rejected.
safe.directorywould let stock libgit2 consumers (those lacking thenon-elevated-admin-owneroverlay) open an Administrators-owned repo regardless of owner. But:safe.directoryin repo-local config (security), so it only works from global/user or system config — there is no clean repo-scoped place to write it.--globalunder an elevated clone lands in the shadow admin's profile (ADMIN_<user>\.gitconfig), which does the real user no good.microsoft/githas the Administrators-membership fix, and GVFS's bundled libgit2 carries the overlay. Only non-GVFS third-party stock-libgit2 tools are unaffected by the fix, and the non-AP elevated-clone path likewise relies purely on ownership (nosafe.directory).Leaving
safe.directoryuntouched keeps the AP and non-AP paths symmetric and avoids cross-user config mutation.Changes
GVFS.Platform.Windows/WindowsPlatform.cs— addIsCurrentUserAdminProtectionShadowAccount()(inspects the current token + ProfileList).GVFS.Platform.Windows/WindowsFileSystem.cs— inEnsureDirectoryIsOwnedByCurrentUser, skip the Administrators→current-user reassignment when the current user is an AP shadow admin.Testing
Builds clean (
GVFS.Platform.Windows, 0 warnings / 0 errors).Detection logic validated on a real AP machine against the live SIDs:
S-1-12-1-…→IsAccountSid=False→ not shadow (reassignment proceeds).S-1-5-21-…-1001→ profileADMIN_<user>→ shadow (reassignment skipped).End-to-end, on a real AP machine in the effective-AP state, using an installed build of this branch:
gvfs clone(runs as shadow admin)srcBUILTIN\Administrators(notadmin_<user>)src\.gitBUILTIN\Administratorsgit status/git rev-parsedubious ownershiperrorPre-fix, those directories would have been owned by the shadow admin, and the real user would have hit
fatal: detected dubious ownership.Related
4e23d28a).