Skip to content

Dns.GetHostAddressesAsync fails with SocketException when called during impersonation #29935

Closed
@davidsh

Description

@davidsh

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, #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 #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 #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.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions