runtime: Dns.GetHostAddressesAsync fails with SocketException when called during impersonation
It seems that WindowsIdentity.RunImpersonated() works differently in .NET Core compared with .NET Framework. This is causing a variety of issues including one affecting ASP.NET Core, dotnet/runtime#29351.
There is some difference in the way that the identity token permissions are getting set on the impersonated token. This is causing “access denied” issues in a variety of ways.
Consider the following repro program included in this issue.
Program.cs
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Security.Principal;
using Microsoft.Win32.SafeHandles;
namespace ImpersonateTest
{
class Program
{
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUser(
string username,
string domain,
string password,
int logonType,
int logonProvider,
out SafeAccessTokenHandle token);
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
const int LOGON_TYPE_NETWORK = 3;
const int LOGON_TYPE_NEW_CREDENTIALS = 9;
static void Main(string[] args)
{
Console.WriteLine($"(Framework: {Path.GetDirectoryName(typeof(object).Assembly.Location)})");
SafeAccessTokenHandle tokenin;
bool returnValue = LogonUser("test1", ".", "****", LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out tokenin); // ** Fails on .NET Core
//bool returnValue = LogonUser("test1", ".", "****", LOGON_TYPE_NETWORK, LOGON32_PROVIDER_DEFAULT, out tokenin); // ** Works on .NET Core
Debug.Assert(returnValue);
Run(tokenin);
tokenin.Dispose();
}
static void Run(SafeAccessTokenHandle token)
{
WindowsIdentity.RunImpersonated(token, () =>
{
RunDnsTest();
RunSocketsHttpHandlerTest();
RunWinHttpHandlerTest();
});
}
static void RunSocketsHttpHandlerTest()
{
try
{
var client = new HttpClient();
HttpResponseMessage response = client.GetAsync("http://corefx-net.cloudapp.net/echo.ashx").GetAwaiter().GetResult();
Console.WriteLine($"{WindowsIdentity.GetCurrent().Name} {WindowsIdentity.GetCurrent().ImpersonationLevel}");
Console.WriteLine($"{(int)response.StatusCode} {response.ReasonPhrase}");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
static void RunWinHttpHandlerTest()
{
try
{
var handler = new WinHttpHandler();
var client = new HttpClient(handler);
HttpResponseMessage response = client.GetAsync("http://corefx-net.cloudapp.net/echo.ashx").GetAwaiter().GetResult();
Console.WriteLine($"{WindowsIdentity.GetCurrent().Name} {WindowsIdentity.GetCurrent().ImpersonationLevel}");
Console.WriteLine($"{(int)response.StatusCode} {response.ReasonPhrase}");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
static void RunDnsTest()
{
try
{
string host = "www.google.it";
Console.WriteLine($"{WindowsIdentity.GetCurrent().Name} {WindowsIdentity.GetCurrent().ImpersonationLevel}");
Console.WriteLine($"Dns.GetHostAddressesAsync({host}) " + Dns.GetHostAddressesAsync(host).Result[0].ToString());
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
}
ImpersonateTest.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp2.2;netcoreapp3.0;net47</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Net.Http.WinHttpHandler" Version="4.5.4" />
<PackageReference Include="System.Security.Principal" Version="4.3.0" />
<PackageReference Include="System.Security.Principal.Windows" Version="4.5.1" />
</ItemGroup>
<!-- Conditionally obtain references for the .NET Framework 4.7 target -->
<ItemGroup Condition=" '$(TargetFramework)' == 'net47' ">
<Reference Include="System.Net.Http" />
</ItemGroup>
</Project>
To demonstrate the repro, create a local machine account (different from the one you use to run this repro) on the Windows machine. It doesn’t matter if it belongs to the “Administrators” group or not.
On .NET Framework, the repro works fine with either LOGON32_LOGON_INTERACTIVE or LOGON_TYPE_NETWORK being used to create the impersonated identity. But .NET Core shows a variety of problems with using LOGON32_LOGON_INTERACTIVE. This repro is a simplified version of the ASP.NET Core issue dotnet/runtime#29351 which is presumably using a logged on identity similar to LOGON32_LOGON_INTERACTIVE.
The problems on .NET Core are the same using .NET Core 2.2 or .NET Core 3.0 Preview 6.
There are three tests in this repro. In one case, the System.IO.FileLoadException is not even catch’able. In my repro here, I have created a secondary Windows account called “test1”.
Success case:
S:\dotnet\ImpersonateTest>dotnet run -f netcoreapp2.2
(Framework: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.2.5)
DSHULMAN-REPRO1\test1 Impersonation
Dns.GetHostAddressesAsync(www.google.it) 2607:f8b0:400a:800::2003
DSHULMAN-REPRO1\test1 Impersonation
200 OK
DSHULMAN-REPRO1\test1 Impersonation
200 OK
Failure case:
S:\dotnet\ImpersonateTest>dotnet run -f netcoreapp2.2
(Framework: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.2.5)
DSHULMAN-REPRO1\test1 Impersonation
System.AggregateException: One or more errors occurred. (This is usually a temporary error during hostname resolution and means that the local server did not receive a response from an authoritative server)
---> System.Net.Sockets.SocketException: This is usually a temporary error during hostname resolution and means that the local server did not receive a response from an authoritative server
at System.Net.Dns.HostResolutionEndHelper(IAsyncResult asyncResult)
at System.Net.Dns.EndGetHostAddresses(IAsyncResult asyncResult)
at System.Net.Dns.<>c.<GetHostAddressesAsync>b__25_1(IAsyncResult asyncResult)
at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
at ImpersonateTest.Program.RunDnsTest() in S:\dotnet\ImpersonateTest\Program.cs:line 87
---> (Inner Exception #0) System.Net.Sockets.SocketException (11002): This is usually a temporary error during hostname resolution and means that the local server did not receive a response from an authoritative server
at System.Net.Dns.HostResolutionEndHelper(IAsyncResult asyncResult)
at System.Net.Dns.EndGetHostAddresses(IAsyncResult asyncResult)
at System.Net.Dns.<>c.<GetHostAddressesAsync>b__25_1(IAsyncResult asyncResult)
at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)<---
System.Net.Http.HttpRequestException: This is usually a temporary error during hostname resolution and means that the local server did not receive a response from an authoritative server
---> System.Net.Sockets.SocketException: This is usually a temporary error during hostname resolution and means that the local server did not receive a response from an authoritative server
at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)
at System.Threading.Tasks.ValueTask`1.get_Result()
at System.Net.Http.HttpConnectionPool.CreateConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Threading.Tasks.ValueTask`1.get_Result()
at System.Net.Http.HttpConnectionPool.WaitForCreatedConnectionAsync(ValueTask`1 creationTask)
at System.Threading.Tasks.ValueTask`1.get_Result()
at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
at ImpersonateTest.Program.RunSocketsHttpHandlerTest() in S:\dotnet\ImpersonateTest\Program.cs:line 55
Unhandled Exception: System.IO.FileLoadException: Could not load file or assembly 'System.Net.Http.WinHttpHandler, Version=4.0.3.2, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. Access is denied.
at ImpersonateTest.Program.RunWinHttpHandlerTest()
at ImpersonateTest.Program.<>c.<Run>b__6_0() in S:\dotnet\ImpersonateTest\Program.cs:line 46
at System.Security.Principal.WindowsIdentity.<>c__DisplayClass64_0.<RunImpersonatedInternal>b__0(Object <p0>)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location where exception was thrown ---
at System.Security.Principal.WindowsIdentity.RunImpersonatedInternal(SafeAccessTokenHandle token, Action action)
at System.Security.Principal.WindowsIdentity.RunImpersonated(SafeAccessTokenHandle safeAccessTokenHandle, Action action)
at ImpersonateTest.Program.Run(SafeAccessTokenHandle token) in S:\dotnet\ImpersonateTest\Program.cs:line 42
at ImpersonateTest.Program.Main(String[] args) in S:\dotnet\ImpersonateTest\Program.cs:line 35
In the RunSocketsHttpHandlerTest() and RunDnsTest(), the error:
System.Net.Sockets.SocketException: This is usually a temporary error during hostname resolution and means that the local server did not receive a response from an authoritative server
is being caused by the Win32 API GetAddrInfoExW() returning WSATRY_AGAIN error. This error is occurring immediately after calling the API. .NET Core is using GetAddrInfoExW() instead of GetAddrInfoW() because the former supports async (via overlapped callback). The GetAddrInfoW() API doesn’t seem to be affected. .NET Framework doesn’t use GetAddrInfoExW() so it isn’t affected. I suspect that GetAddrInfoExW() is returning WSATRY_AGAIN due to the same access permissions problem running in the WindowsIdentity.RunImpersonated() context.
We also have dotnet/runtime#28460 which is a related problem with impersonation where DNS resolution is not working. That’s probably due to the same GetAddrInfoExW() problem here.
This seems like a compatibility break from .NET Framework in how WindowsIdentity.RunImpersonated() behaves.
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 5
- Comments: 72 (40 by maintainers)
We’ll try to have someone investigate.
@stephentoub and I took a look at this issue offline and I wanted to share the results.
The
FileLoadException
issue is a result of the impersonated user not having read permission to the resource being accessed. The same issue can be reproduced in .NETFramework. When using impersonation in an application you need to make sure the impersonated user has access to read the application binaries as those binaries may be loaded by the runtime while the thread is impersonating. You can also avoid this sort of problem by “warming up” the codepath: loading everything as a regular user before impersonating. Since this isn’t a new issue let’s table that aspect of the original report.The primary issue here is with
GetHostAddressesAsync
. This method was changed in https://github.com/dotnet/corefx/commit/d3ff31e3b9e8c7b1e6003196650bae4a658ef889 (.NETCore 2.1) to useGetAddrInfoExW
to make it actually async. There seems to be a problem withGetAddrInfoExW
that whenever it’s run under impersonation it will fail. We were able to reproduce the same failure by porting a portion of this code to .NETFramework. This was previously correctly noted by @wzchua https://github.com/dotnet/runtime/issues/29935#issuecomment-734823538.This appears to be due to the async handling in ws2_32:
This will call OpenThreadToken(…, OpenAsSelf=false, …) which fails with ACCESS_DENIED, which ws2_32 will remap to WSATRY_AGAIN. We’ll follow up with the Windows owners of ws2_32.dll to understand if this is expected or not.
Given this behavior I think we should avoid calling ws2_32’s async methods when run under impersonation. Changing the area path as appropriate.
I like that approach (calling the sync method asynchronously as a fallback just as we already do if the overlapped support isn’t available). It’s simple, avoids penalizing the common case, and automatically becomes a nop if/when the underlying issue is addressed. There are other causes of WSATRY_AGAIN, but they should be rare, and it should be inconsequential if we effectively retry once when they occur.
Fixed in 6.0/master (PR #45816) and in 5.0.3 (PR #46897) … applies only to Windows OS EXCEPT Win8 and Win 2012 (that fix is tracked by #45165).
either way is fine. I think it would be nice to get some feedback before we rush to 3.x. It is broken for very long time so I doubt one month would really matter.
Also for the record, the discussion with Windows team is still going on and OS fix may or may not be available.
They would strongly prefer that we have a customer who has tried out the privates though.
@karelz same template, same label, but of course the PR is against corefx repo. The bar is higher, but multiple customers blocked from moving to Core is a strong argument. This seems worth a try. If it is ready for tactics tomorrow it might be possible to merge it by Friday which is the cutoff for Febuary.
Call the synchronous method as fallback when the asynchronous one returns
WSATRY_AGAIN
?I’ve tested with netframework 4.7
The Impersonate does not work with
GetAddrInfoExW
Would the process running the impersonation be required to be running as a Windows Service?
@Vaccano I have tried this with 3.1 and 5 without any success. You can see my GitHub issue here: #45165
This is a workaround for .Net Core 3.1:
Alternativly you can do this:
But that setting (
System.Net.Http.UseSocketsHttpHandler
) was removed from .Net 5.0. As such, if you need impersonation, and are affected by this issue, you cannot upgrade to .Net 5.0.Seems odd that Impersonation has such a core bug and that it just keeps getting kicked down the road. (As I understand it, the 6.0.0 milestone is basically saying that this has to wait another year for a hope of a fix.)
Hi! Any updates regarding this issue? Im experiencing the same issue using impersonation (WindowsIdentity.RunImpersonated) with ASP.net Core 3.1 while trying to impersonate a request using a HttpClient (I have had some success doing this using a modified version of WebClient… however… that one ended up with a problem resolving DNS which has been described previously in this issue). This currently prevents a big client for my company to develop a new application using .Net Core, so for now it seems like we will use Framework instead.
And as alaitang previously mentioned, the DNS problem seems to be related to a DNS lookup somehow… since the exception is gone as soon as you target an IP as host, instead of a DNS (However… that got me stuck with the access denied issue instead)