diff --git a/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs b/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs index 920cf1214c08..4ceed132dabc 100644 --- a/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs +++ b/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs @@ -209,6 +209,46 @@ private WindowsIdentity(IntPtr userToken, string authType, int isAuthenticated) _isAuthenticated = isAuthenticated; } + private static SafeAccessTokenHandle DuplicateAccessToken(IntPtr accessToken) + { + SafeAccessTokenHandle duplicateAccessToken = SafeAccessTokenHandle.InvalidHandle; + IntPtr currentProcessHandle = Interop.Kernel32.GetCurrentProcess(); + if (!Interop.Kernel32.DuplicateHandle( + currentProcessHandle, + accessToken, + currentProcessHandle, + ref duplicateAccessToken, + 0, + true, + Interop.DuplicateHandleOptions.DUPLICATE_SAME_ACCESS)) + { + throw new SecurityException(new Win32Exception().Message); + } + + return duplicateAccessToken; + } + + private static SafeAccessTokenHandle DuplicateAccessToken(SafeAccessTokenHandle accessToken) + { + if (accessToken.IsInvalid) + { + return accessToken; + } + + bool refAdded = false; + try + { + accessToken.DangerousAddRef(ref refAdded); + return DuplicateAccessToken(accessToken.DangerousGetHandle()); + } + finally + { + if (refAdded) + { + accessToken.DangerousRelease(); + } + } + } private void CreateFromToken(IntPtr userToken) { @@ -222,14 +262,7 @@ private void CreateFromToken(IntPtr userToken) if (Marshal.GetLastWin32Error() == Interop.Errors.ERROR_INVALID_HANDLE) throw new ArgumentException(SR.Argument_InvalidImpersonationToken); - if (!Interop.Kernel32.DuplicateHandle(Interop.Kernel32.GetCurrentProcess(), - userToken, - Interop.Kernel32.GetCurrentProcess(), - ref _safeTokenHandle, - 0, - true, - Interop.DuplicateHandleOptions.DUPLICATE_SAME_ACCESS)) - throw new SecurityException(new Win32Exception().Message); + _safeTokenHandle = DuplicateAccessToken(userToken); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2229", Justification = "Public API has already shipped.")] @@ -640,6 +673,8 @@ public void Dispose() private static void RunImpersonatedInternal(SafeAccessTokenHandle token, Action action) { + token = DuplicateAccessToken(token); + bool isImpersonating; int hr; SafeAccessTokenHandle previousToken = GetCurrentToken(TokenAccessLevels.MaximumAllowed, false, out isImpersonating, out hr); diff --git a/src/System.Security.Principal.Windows/tests/System.Security.Principal.Windows.Tests.csproj b/src/System.Security.Principal.Windows/tests/System.Security.Principal.Windows.Tests.csproj index f0daf2c1b900..ab70abfa35fb 100644 --- a/src/System.Security.Principal.Windows/tests/System.Security.Principal.Windows.Tests.csproj +++ b/src/System.Security.Principal.Windows/tests/System.Security.Principal.Windows.Tests.csproj @@ -14,5 +14,10 @@ Common\System\Runtime\Serialization\Formatters\BinaryFormatterHelpers.cs + + + CommonTest\System\Threading\ThreadTestHelpers.cs + + \ No newline at end of file diff --git a/src/System.Security.Principal.Windows/tests/WindowsIdentityTests.cs b/src/System.Security.Principal.Windows/tests/WindowsIdentityTests.cs index 5445388cd139..95859cd8eee7 100644 --- a/src/System.Security.Principal.Windows/tests/WindowsIdentityTests.cs +++ b/src/System.Security.Principal.Windows/tests/WindowsIdentityTests.cs @@ -5,8 +5,12 @@ using Microsoft.Win32.SafeHandles; using System; using System.Linq; +using System.Runtime.CompilerServices; using System.Security.Claims; using System.Security.Principal; +using System.Threading; +using System.Threading.Tasks; +using System.Threading.Tests; using Xunit; public class WindowsIdentityTests @@ -120,6 +124,56 @@ public static void CheckUserClaims() } } + [Fact] + public static void RunImpersonatedAsyncTest() + { + var testData = new RunImpersonatedAsyncTestInfo(); + BeginTask(testData); + + // Wait for the SafeHandle that was disposed in BeginTask() to actually be closed + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.WaitForPendingFinalizers(); + + testData.continueTask.Release(); + testData.task.CheckedWait(); + if (testData.exception != null) + { + throw new AggregateException(testData.exception); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void BeginTask(RunImpersonatedAsyncTestInfo testInfo) + { + testInfo.continueTask = new SemaphoreSlim(0, 1); + using (SafeAccessTokenHandle token = WindowsIdentity.GetCurrent().AccessToken) + { + WindowsIdentity.RunImpersonated(token, () => + { + testInfo.task = Task.Run(async () => + { + try + { + Task task = testInfo.continueTask.WaitAsync(ThreadTestHelpers.UnexpectedTimeoutMilliseconds); + Assert.True(await task.ConfigureAwait(false)); + } + catch (Exception ex) + { + testInfo.exception = ex; + } + }); + }); + } + } + + private class RunImpersonatedAsyncTestInfo + { + public Task task; + public SemaphoreSlim continueTask; + public Exception exception; + } + private static void CheckDispose(WindowsIdentity identity, bool anonymous = false) { Assert.False(identity.AccessToken.IsClosed);