Skip to content

Commit 3d9146b

Browse files
authored
Protect WAM account picker deadlocks on STA thread apartments (Azure#36061)
1 parent 7da9223 commit 3d9146b

File tree

3 files changed

+67
-8
lines changed

3 files changed

+67
-8
lines changed

sdk/identity/Azure.Identity.BrokeredAuthentication/tests/ManualInteractiveBrowserCredentialBrokerTests.cs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// Licensed under the MIT License.
33

44
using System;
5+
using System.Linq;
56
using System.Runtime.InteropServices;
7+
using System.Threading;
68
using System.Threading.Tasks;
79
using Azure.Core;
810
using NUnit.Framework;
@@ -11,6 +13,8 @@ namespace Azure.Identity.BrokeredAuthentication.Tests
1113
{
1214
public class ManualInteractiveBrowserCredentialBrokerTests
1315
{
16+
private static TokenRequestContext context = new TokenRequestContext(new string[] { "https://vault.azure.net/.default" });
17+
1418
[DllImport("user32.dll")]
1519
private static extern IntPtr GetForegroundWindow();
1620

@@ -28,5 +32,62 @@ public async Task AuthenticateWithBrokerAsync()
2832

2933
Assert.NotNull(token.Token);
3034
}
35+
36+
[Test]
37+
[Ignore("This test is an integration test which can only be run with user interaction")]
38+
public void AuthenticateWithBrokerAsyncWithSTA([Values(true, false)] bool isAsync)
39+
{
40+
var assemblies = System.Reflection.Assembly.GetExecutingAssembly().GetReferencedAssemblies().FirstOrDefault(a => a.Name == "System.Windows.Forms");
41+
if (assemblies != null)
42+
{
43+
throw new Exception("has winforms");
44+
}
45+
ManualResetEventSlim evt = new();
46+
Thread thread = new Thread(async () =>
47+
{
48+
// do something with retVal
49+
50+
Console.WriteLine($"Thread {Thread.CurrentThread.GetApartmentState()}");
51+
IntPtr parentWindowHandle = GetForegroundWindow();
52+
var options = new InteractiveBrowserCredentialBrokerOptions(parentWindowHandle)
53+
{
54+
TokenCachePersistenceOptions = new()
55+
};
56+
57+
var cred = new InteractiveBrowserCredential(options);
58+
var authRecord = isAsync ? await cred.AuthenticateAsync(context) : cred.Authenticate(context);
59+
options.AuthenticationRecord = authRecord;
60+
AccessToken token = isAsync ? await cred.GetTokenAsync(context).ConfigureAwait(false) : cred.GetToken(context);
61+
Console.WriteLine("got token");
62+
evt.Set();
63+
// });
64+
});
65+
66+
Thread thread2 = new Thread(async () =>
67+
{
68+
// do something with retVal
69+
70+
Console.WriteLine($"Thread {Thread.CurrentThread.GetApartmentState()}");
71+
IntPtr parentWindowHandle = GetForegroundWindow();
72+
var options = new InteractiveBrowserCredentialBrokerOptions(parentWindowHandle)
73+
{
74+
TokenCachePersistenceOptions = new()
75+
};
76+
77+
var cred = new InteractiveBrowserCredential(options);
78+
var authRecord = isAsync ? await cred.AuthenticateAsync(context) : cred.Authenticate(context);
79+
options.AuthenticationRecord = authRecord;
80+
AccessToken token = isAsync ? await cred.GetTokenAsync(context).ConfigureAwait(false) : cred.GetToken(context);
81+
Console.WriteLine("got token");
82+
thread.Start();
83+
// });
84+
});
85+
#pragma warning disable CA1416 // Validate platform compatibility
86+
thread.SetApartmentState(ApartmentState.STA);
87+
thread2.SetApartmentState(ApartmentState.STA);
88+
#pragma warning restore CA1416 // Validate platform compatibility
89+
thread2.Start();
90+
evt.Wait(10000);
91+
}
3192
}
3293
}

sdk/identity/Azure.Identity/src/MsalPublicClient.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,9 @@ protected virtual async ValueTask<AuthenticationResult> AcquireTokenSilentCoreAs
126126

127127
public async ValueTask<AuthenticationResult> AcquireTokenInteractiveAsync(string[] scopes, string claims, Prompt prompt, string loginHint, string tenantId, bool async, CancellationToken cancellationToken)
128128
{
129-
#pragma warning disable AZC0109 // Misuse of 'async' parameter.
130-
if (!async && !IdentityCompatSwitches.DisableInteractiveBrowserThreadpoolExecution)
131-
#pragma warning restore AZC0109 // Misuse of 'async' parameter.
129+
if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA && !IdentityCompatSwitches.DisableInteractiveBrowserThreadpoolExecution)
132130
{
133-
// In the synchronous case we need to use Task.Run to execute on the call to MSAL on the threadpool.
131+
// In the case we are called in an STA apartment thread, we need to use Task.Run to execute on the call to MSAL on the threadpool.
134132
// On certain platforms MSAL will use the embedded browser instead of launching the browser as a separate
135133
// process. Executing with Task.Run prevents possibly deadlocking the UI thread in these cases.
136134
// This workaround can be disabled by using the "Azure.Identity.DisableInteractiveBrowserThreadpoolExecution" app switch

sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,16 +144,16 @@ public async Task InteractiveBrowserValidateSyncWorkaroundCompatSwitch()
144144
// neither Environment variable or AppContext switch is set.
145145
// environment variable is set and AppContext switch is not set
146146
// AppContext switch is set
147-
await ValidateSyncWorkaroundCompatSwitch(!IsAsync);
147+
await ValidateSyncWorkaroundCompatSwitch(Thread.CurrentThread.GetApartmentState() == ApartmentState.STA);
148148

149149
using (var envVar = new TestEnvVar("AZURE_IDENTITY_DISABLE_INTERACTIVEBROWSERTHREADPOOLEXECUTION", string.Empty))
150150
{
151-
await ValidateSyncWorkaroundCompatSwitch(!IsAsync);
151+
await ValidateSyncWorkaroundCompatSwitch(Thread.CurrentThread.GetApartmentState() == ApartmentState.STA);
152152
}
153153

154154
using (var envVar = new TestEnvVar("AZURE_IDENTITY_DISABLE_INTERACTIVEBROWSERTHREADPOOLEXECUTION", "false"))
155155
{
156-
await ValidateSyncWorkaroundCompatSwitch(!IsAsync);
156+
await ValidateSyncWorkaroundCompatSwitch(Thread.CurrentThread.GetApartmentState() == ApartmentState.STA);
157157
}
158158

159159
using (var envVar = new TestEnvVar("AZURE_IDENTITY_DISABLE_INTERACTIVEBROWSERTHREADPOOLEXECUTION", "true"))
@@ -172,7 +172,7 @@ public async Task InteractiveBrowserValidateSyncWorkaroundCompatSwitch()
172172

173173
AppContext.SetSwitch("Azure.Identity.DisableInteractiveBrowserThreadpoolExecution", false);
174174

175-
await ValidateSyncWorkaroundCompatSwitch(!IsAsync);
175+
await ValidateSyncWorkaroundCompatSwitch(Thread.CurrentThread.GetApartmentState() == ApartmentState.STA);
176176
}
177177

178178
[Test]

0 commit comments

Comments
 (0)