-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Duplicate the access token passed to WindowsIdentity.RunImpersonated #30346
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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(); | ||
| } | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than declaring the DllImport twice, you could also just do the AddRef/Release yourself: SafeAccessTokenHandle duplicateAccessToken;
bool success = false;
try
{
accessToken.DangerousAddRef(ref success);
duplicateAccessToken = DuplicateAccessToken(accessToken.DangerousGetHandle());
}
finally
{
if (success) accessToken.DangerousRelease();
}
return duplicateAccessToken;
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the purpose of the bool return? It seems like it always either returns true or throws.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as the ref bool argument to Monitor.Enter: reliability on platforms with thread aborts.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if success is false after DangerousAddRef? Should it throw ObjectDisposedException? Maybe I can just completely ignore the return value.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It won't be: as you said, it'll either be true or DangerousAddRef will throw. The ref bool is there to support thread aborts. On systems with thread aborts, you put the DangerousAddRef call inside the try, and then if there's an exception, the finally can look at the bool to determine whether it needs to DangerousRelease. If the DangerousAddRef were outside the try, a thread abort could occur after a successful AddRef but before entering the try, and the SafeHandle would be leaked.
On .NET Core, you can. Technically aborts are still possible from the debugger, but that's not a key reliability scenario. |
||
| } | ||
|
|
||
| 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); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the new handle get disposed of correctly eventually?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The handle will be closed once the GC determines there are no more references and the SafeHandle is finalized
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So we're depending on finalization. Is that how things work in netfx, too?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea, there's no other clear way of identifying the proper lifetime for the handle from the API side |
||
| } | ||
|
|
||
| [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); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why SecurityException? Is that what netfx throws?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see, you just moved the code from below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Netfx actually creates/saves the current WindowsIdentity after impersonation, but yes any failure to duplicate the access token results in a SecurityException in Netfx.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And in Core in any of the WindowsIdentity constructors that take an IntPtr access token