Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 6ac8b2a

Browse files
kouvelstephentoub
authored andcommitted
[release/2.1] Duplicate the access token passed to WindowsIdentity.RunImpersonated (#30346) (#30379)
* Duplicate the access token passed to WindowsIdentity.RunImpersonated (#30346) So that callbacks for async work initiated while impersonated may continue to impersonate even after the original access token had been disposed. Port of #30346 and #30377 to release/2.1 Fixes https://github.com/dotnet/corefx/issues/30275 * Small fix * Address feedback * Change to use ref counting, remove assert (no null check on access token parameter) * Manual add/release * Add test * Change test
1 parent d001a29 commit 6ac8b2a

File tree

3 files changed

+130
-14
lines changed

3 files changed

+130
-14
lines changed

src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -209,27 +209,68 @@ private WindowsIdentity(IntPtr userToken, string authType, int isAuthenticated)
209209
_isAuthenticated = isAuthenticated;
210210
}
211211

212-
213-
private void CreateFromToken(IntPtr userToken)
212+
private static SafeAccessTokenHandle DuplicateAccessToken(IntPtr accessToken)
214213
{
215-
if (userToken == IntPtr.Zero)
214+
if (accessToken == IntPtr.Zero)
215+
{
216216
throw new ArgumentException(SR.Argument_TokenZero);
217+
}
217218

218219
// Find out if the specified token is a valid.
219-
uint dwLength = (uint)sizeof(uint);
220-
bool result = Interop.Advapi32.GetTokenInformation(userToken, (uint)TokenInformationClass.TokenType,
221-
SafeLocalAllocHandle.InvalidHandle, 0, out dwLength);
222-
if (Marshal.GetLastWin32Error() == Interop.Errors.ERROR_INVALID_HANDLE)
220+
uint dwLength = sizeof(uint);
221+
if (!Interop.Advapi32.GetTokenInformation(
222+
accessToken,
223+
(uint)TokenInformationClass.TokenType,
224+
IntPtr.Zero,
225+
0,
226+
out dwLength) &&
227+
Marshal.GetLastWin32Error() == Interop.Errors.ERROR_INVALID_HANDLE)
228+
{
223229
throw new ArgumentException(SR.Argument_InvalidImpersonationToken);
230+
}
224231

225-
if (!Interop.Kernel32.DuplicateHandle(Interop.Kernel32.GetCurrentProcess(),
226-
userToken,
227-
Interop.Kernel32.GetCurrentProcess(),
228-
ref _safeTokenHandle,
229-
0,
230-
true,
231-
Interop.DuplicateHandleOptions.DUPLICATE_SAME_ACCESS))
232+
SafeAccessTokenHandle duplicateAccessToken = SafeAccessTokenHandle.InvalidHandle;
233+
IntPtr currentProcessHandle = Interop.Kernel32.GetCurrentProcess();
234+
if (!Interop.Kernel32.DuplicateHandle(
235+
currentProcessHandle,
236+
accessToken,
237+
currentProcessHandle,
238+
ref duplicateAccessToken,
239+
0,
240+
true,
241+
Interop.DuplicateHandleOptions.DUPLICATE_SAME_ACCESS))
242+
{
232243
throw new SecurityException(new Win32Exception().Message);
244+
}
245+
246+
return duplicateAccessToken;
247+
}
248+
249+
private static SafeAccessTokenHandle DuplicateAccessToken(SafeAccessTokenHandle accessToken)
250+
{
251+
if (accessToken.IsInvalid)
252+
{
253+
return accessToken;
254+
}
255+
256+
bool refAdded = false;
257+
try
258+
{
259+
accessToken.DangerousAddRef(ref refAdded);
260+
return DuplicateAccessToken(accessToken.DangerousGetHandle());
261+
}
262+
finally
263+
{
264+
if (refAdded)
265+
{
266+
accessToken.DangerousRelease();
267+
}
268+
}
269+
}
270+
271+
private void CreateFromToken(IntPtr userToken)
272+
{
273+
_safeTokenHandle = DuplicateAccessToken(userToken);
233274
}
234275

235276
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2229", Justification = "Public API has already shipped.")]
@@ -640,6 +681,8 @@ public void Dispose()
640681

641682
private static void RunImpersonatedInternal(SafeAccessTokenHandle token, Action action)
642683
{
684+
token = DuplicateAccessToken(token);
685+
643686
bool isImpersonating;
644687
int hr;
645688
SafeAccessTokenHandle previousToken = GetCurrentToken(TokenAccessLevels.MaximumAllowed, false, out isImpersonating, out hr);

src/System.Security.Principal.Windows/tests/System.Security.Principal.Windows.Tests.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,10 @@
1414
<Link>Common\System\Runtime\Serialization\Formatters\BinaryFormatterHelpers.cs</Link>
1515
</Compile>
1616
</ItemGroup>
17+
<ItemGroup>
18+
<Compile Include="$(CommonTestPath)\System\Threading\ThreadTestHelpers.cs">
19+
<Link>CommonTest\System\Threading\ThreadTestHelpers.cs</Link>
20+
</Compile>
21+
</ItemGroup>
1722
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
1823
</Project>

src/System.Security.Principal.Windows/tests/WindowsIdentityTests.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@
55
using Microsoft.Win32.SafeHandles;
66
using System;
77
using System.Linq;
8+
using System.Runtime.CompilerServices;
89
using System.Security.Claims;
910
using System.Security.Principal;
11+
using System.Threading;
12+
using System.Threading.Tasks;
13+
using System.Threading.Tests;
1014
using Xunit;
1115

1216
public class WindowsIdentityTests
@@ -120,6 +124,70 @@ public static void CheckUserClaims()
120124
}
121125
}
122126

127+
[Fact]
128+
public static void RunImpersonatedTest_InvalidHandle()
129+
{
130+
using (var mutex = new Mutex())
131+
{
132+
Assert.Throws<ArgumentException>(() =>
133+
{
134+
WindowsIdentity.RunImpersonated(
135+
new SafeAccessTokenHandle(mutex.SafeWaitHandle.DangerousGetHandle()),
136+
() => { });
137+
});
138+
}
139+
}
140+
141+
[Fact]
142+
public static void RunImpersonatedAsyncTest()
143+
{
144+
var testData = new RunImpersonatedAsyncTestInfo();
145+
BeginTask(testData);
146+
147+
// Wait for the SafeHandle that was disposed in BeginTask() to actually be closed
148+
GC.Collect();
149+
GC.WaitForPendingFinalizers();
150+
GC.WaitForPendingFinalizers();
151+
152+
testData.continueTask.Release();
153+
testData.task.Wait(ThreadTestHelpers.UnexpectedTimeoutMilliseconds);
154+
if (testData.exception != null)
155+
{
156+
throw new AggregateException(testData.exception);
157+
}
158+
}
159+
160+
[MethodImpl(MethodImplOptions.NoInlining)]
161+
private static void BeginTask(RunImpersonatedAsyncTestInfo testInfo)
162+
{
163+
testInfo.continueTask = new SemaphoreSlim(0, 1);
164+
using (SafeAccessTokenHandle token = WindowsIdentity.GetCurrent().AccessToken)
165+
{
166+
WindowsIdentity.RunImpersonated(token, () =>
167+
{
168+
testInfo.task = Task.Run(async () =>
169+
{
170+
try
171+
{
172+
Task<bool> task = testInfo.continueTask.WaitAsync(ThreadTestHelpers.UnexpectedTimeoutMilliseconds);
173+
Assert.True(await task.ConfigureAwait(false));
174+
}
175+
catch (Exception ex)
176+
{
177+
testInfo.exception = ex;
178+
}
179+
});
180+
});
181+
}
182+
}
183+
184+
private class RunImpersonatedAsyncTestInfo
185+
{
186+
public Task task;
187+
public SemaphoreSlim continueTask;
188+
public Exception exception;
189+
}
190+
123191
private static void CheckDispose(WindowsIdentity identity, bool anonymous = false)
124192
{
125193
Assert.False(identity.AccessToken.IsClosed);

0 commit comments

Comments
 (0)