Skip to content

Commit 301f888

Browse files
authored
Merge pull request #344 from mjcheetham/wam-elevate
Add WAM workaround for elevated processes
2 parents 5dc3657 + b3cbf05 commit 301f888

File tree

7 files changed

+208
-9
lines changed

7 files changed

+208
-9
lines changed

docs/wam.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Web Account Manager integration
2+
3+
## Running as administrator
4+
5+
The Windows broker ("WAM") makes heavy use of
6+
[COM](https://docs.microsoft.com/en-us/windows/win32/com/the-component-object-model),
7+
an IPC and RPC technology built-in to the Windows operating system. In order to
8+
integrate with WAM, Git Credential Manager and the underlying
9+
[Microsoft Authentication Library (MSAL)](https://aka.ms/msal-net)
10+
must use COM interfaces and remote procedure calls (RPC).
11+
12+
When you run Git Credential Manager as an elevated process, such as when you run
13+
a `git` command from an Administrator command-prompt or perform Git operations
14+
from Visual Studio running as Administrator, some of the calls made between GCM
15+
and WAM may fail due to differing process security levels.
16+
17+
If you have enabled using the broker and GCM detects it is running in an
18+
elevated process, it will automatically attempt to modify the COM security
19+
settings for the running process so that GCM and WAM can work together.
20+
21+
However, this automatic process security change is not guaranteed to succeed
22+
depending on various external factors like registry or system-wide COM settings.
23+
If GCM fails to modify the process COM security settings, a warning message is
24+
printed and use of the broker is disabled for this invocation of GCM:
25+
26+
```text
27+
warning: broker initialization failed
28+
Failed to set COM process security to allow Windows broker from an elevated process (0x80010119).
29+
See https://aka.ms/gcmcore-wamadmin for more information.
30+
```
31+
32+
### Possible solutions
33+
34+
In order to fix the problem there are a few options:
35+
36+
1. Do not run Git or Git Credential Manager as elevated processes.
37+
2. Disable the broker by setting the
38+
[`GCM_MSAUTH_USEBROKER`](environment.md#gcm_msauth_usebroker)
39+
environment variable or the
40+
[`credential.msauthUseBroker`](configuration.md#credentialmsauthusebroker)
41+
Git configuration setting to `false`.

src/shared/Git-Credential-Manager/Program.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
using System;
44
using System.IO;
55
using System.Reflection;
6-
using System.Runtime.InteropServices;
76
using Atlassian.Bitbucket;
87
using GitHub;
98
using Microsoft.AzureRepos;
9+
using Microsoft.Git.CredentialManager.Authentication;
1010

1111
namespace Microsoft.Git.CredentialManager
1212
{
@@ -18,6 +18,22 @@ public static void Main(string[] args)
1818
using (var context = new CommandContext(appPath))
1919
using (var app = new Application(context))
2020
{
21+
// Workaround for https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/2560
22+
if (MicrosoftAuthentication.CanUseBroker(context))
23+
{
24+
try
25+
{
26+
MicrosoftAuthentication.InitializeBroker();
27+
}
28+
catch (Exception ex)
29+
{
30+
context.Streams.Error.WriteLine(
31+
"warning: broker initialization failed{0}{1}",
32+
Environment.NewLine, ex.Message
33+
);
34+
}
35+
}
36+
2137
// Register all supported host providers at the normal priority.
2238
// The generic provider should never win against a more specific one, so register it with low priority.
2339
app.RegisterProvider(new AzureReposHostProvider(context), HostProviderPriority.Normal);

src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Collections.Generic;
55
using System.IO;
66
using System.Net.Http;
7-
using System.Runtime.InteropServices;
87
using System.Threading.Tasks;
98
using Microsoft.Identity.Client;
109
using Microsoft.Identity.Client.Extensions.Msal;
@@ -44,19 +43,74 @@ public class MicrosoftAuthentication : AuthenticationBase, IMicrosoftAuthenticat
4443
"live", "liveconnect", "liveid",
4544
};
4645

46+
#region Broker Initialization
47+
48+
public static bool IsBrokerInitialized { get; private set; }
49+
50+
public static void InitializeBroker()
51+
{
52+
if (IsBrokerInitialized)
53+
{
54+
return;
55+
}
56+
57+
IsBrokerInitialized = true;
58+
59+
// Broker is only supported on Windows 10
60+
if (!PlatformUtils.IsWindows10())
61+
{
62+
return;
63+
}
64+
65+
// Nothing to do when not an elevated user
66+
if (!PlatformUtils.IsElevatedUser())
67+
{
68+
return;
69+
}
70+
71+
// Lower COM security so that MSAL can make the calls to WAM
72+
int result = Interop.Windows.Native.Ole32.CoInitializeSecurity(
73+
IntPtr.Zero,
74+
-1,
75+
IntPtr.Zero,
76+
IntPtr.Zero,
77+
Interop.Windows.Native.Ole32.RpcAuthnLevel.None,
78+
Interop.Windows.Native.Ole32.RpcImpLevel.Impersonate,
79+
IntPtr.Zero,
80+
Interop.Windows.Native.Ole32.EoAuthnCap.None,
81+
IntPtr.Zero
82+
);
83+
84+
if (result != 0)
85+
{
86+
throw new Exception(
87+
$"Failed to set COM process security to allow Windows broker from an elevated process (0x{result:x})." +
88+
Environment.NewLine +
89+
$"See {Constants.HelpUrls.GcmWamComSecurity} for more information.");
90+
}
91+
}
92+
93+
#endregion
94+
4795
public MicrosoftAuthentication(ICommandContext context)
48-
: base(context) {}
96+
: base(context) { }
4997

5098
#region IMicrosoftAuthentication
5199

52100
public async Task<IMicrosoftAuthenticationResult> GetTokenAsync(
53101
string authority, string clientId, Uri redirectUri, string[] scopes, string userName)
54102
{
55103
// Check if we can and should use OS broker authentication
56-
bool useBroker = CanUseBroker();
57-
if (useBroker)
104+
bool useBroker = false;
105+
if (CanUseBroker(Context))
58106
{
59-
Context.Trace.WriteLine("OS broker is available and enabled.");
107+
// Can only use the broker if it has been initialized
108+
useBroker = IsBrokerInitialized;
109+
110+
if (IsBrokerInitialized)
111+
Context.Trace.WriteLine("OS broker is available and enabled.");
112+
else
113+
Context.Trace.WriteLine("OS broker has not been initialized and cannot not be used.");
60114
}
61115

62116
// Create the public client application for authentication
@@ -396,19 +450,19 @@ public HttpClient GetHttpClient()
396450

397451
#region Auth flow capability detection
398452

399-
private bool CanUseBroker()
453+
public static bool CanUseBroker(ICommandContext context)
400454
{
401455
#if NETFRAMEWORK
402456
// We only support the broker on Windows 10 and require an interactive session
403-
if (!Context.SessionManager.IsDesktopSession || !PlatformUtils.IsWindows10())
457+
if (!context.SessionManager.IsDesktopSession || !PlatformUtils.IsWindows10())
404458
{
405459
return false;
406460
}
407461

408462
// Default to not using the OS broker
409463
const bool defaultValue = false;
410464

411-
if (Context.Settings.TryGetSetting(Constants.EnvironmentVariables.MsAuthUseBroker,
465+
if (context.Settings.TryGetSetting(Constants.EnvironmentVariables.MsAuthUseBroker,
412466
Constants.GitConfiguration.Credential.SectionName,
413467
Constants.GitConfiguration.Credential.MsAuthUseBroker,
414468
out string valueStr))

src/shared/Microsoft.Git.CredentialManager/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ public static class HelpUrls
114114
public const string GcmHttpProxyGuide = "https://aka.ms/gcmcore-httpproxy";
115115
public const string GcmTlsVerification = "https://aka.ms/gcmcore-tlsverify";
116116
public const string GcmLinuxCredStores = "https://aka.ms/gcmcore-linuxcredstores";
117+
public const string GcmWamComSecurity = "https://aka.ms/gcmcore-wamadmin";
117118
}
118119

119120
private static Version _gcmVersion;

src/shared/Microsoft.Git.CredentialManager/Interop/Posix/Native/Unistd.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,8 @@ public static class Unistd
2121

2222
[DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
2323
public static extern int getppid();
24+
25+
[DllImport("libc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
26+
public static extern int geteuid();
2427
}
2528
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
using System;
4+
using System.Runtime.InteropServices;
5+
6+
namespace Microsoft.Git.CredentialManager.Interop.Windows.Native
7+
{
8+
public static class Ole32
9+
{
10+
private const string LibraryName = "ole32.dll";
11+
12+
public const uint RPC_E_TOO_LATE = 0x80010119;
13+
14+
[DllImport(LibraryName)]
15+
public static extern int CoInitializeSecurity(
16+
IntPtr pVoid,
17+
int cAuthSvc,
18+
IntPtr asAuthSvc,
19+
IntPtr pReserved1,
20+
RpcAuthnLevel level,
21+
RpcImpLevel impers,
22+
IntPtr pAuthList,
23+
EoAuthnCap dwCapabilities,
24+
IntPtr pReserved3);
25+
26+
public enum RpcAuthnLevel
27+
{
28+
Default = 0,
29+
None = 1,
30+
Connect = 2,
31+
Call = 3,
32+
Pkt = 4,
33+
PktIntegrity = 5,
34+
PktPrivacy = 6
35+
}
36+
37+
public enum RpcImpLevel
38+
{
39+
Default = 0,
40+
Anonymous = 1,
41+
Identify = 2,
42+
Impersonate = 3,
43+
Delegate = 4
44+
}
45+
46+
public enum EoAuthnCap
47+
{
48+
None = 0x00,
49+
MutualAuth = 0x01,
50+
StaticCloaking = 0x20,
51+
DynamicCloaking = 0x40,
52+
AnyAuthority = 0x80,
53+
MakeFullSIC = 0x100,
54+
Default = 0x800,
55+
SecureRefs = 0x02,
56+
AccessControl = 0x04,
57+
AppID = 0x08,
58+
Dynamic = 0x10,
59+
RequireFullSIC = 0x200,
60+
AutoImpersonate = 0x400,
61+
NoCustomMarshal = 0x2000,
62+
DisableAAA = 0x1000
63+
}
64+
}
65+
}

src/shared/Microsoft.Git.CredentialManager/PlatformUtils.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license.
33
using System;
44
using System.Runtime.InteropServices;
5+
using Microsoft.Git.CredentialManager.Interop.Posix.Native;
56

67
namespace Microsoft.Git.CredentialManager
78
{
@@ -136,6 +137,24 @@ public static void EnsurePosix()
136137
}
137138
}
138139

140+
public static bool IsElevatedUser()
141+
{
142+
if (IsWindows())
143+
{
144+
#if NETFRAMEWORK
145+
var identity = System.Security.Principal.WindowsIdentity.GetCurrent();
146+
var principal = new System.Security.Principal.WindowsPrincipal(identity);
147+
return principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
148+
#endif
149+
}
150+
else if (IsPosix())
151+
{
152+
return Unistd.geteuid() == 0;
153+
}
154+
155+
return false;
156+
}
157+
139158
#region Platform information helper methods
140159

141160
private static string GetOSType()

0 commit comments

Comments
 (0)