Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
davidsh opened this issue Jun 18, 2019 · 72 comments · Fixed by #45816
Closed

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

davidsh opened this issue Jun 18, 2019 · 72 comments · Fixed by #45816
Assignees
Labels
area-System.Net tenet-compatibility Incompatibility with previous versions or .NET Framework
Milestone

Comments

@davidsh
Copy link
Contributor

davidsh commented Jun 18, 2019

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.

@davidsh
Copy link
Contributor Author

davidsh commented Jun 18, 2019

cc: @kouvel @stephentoub @danmosemsft

@davidsh
Copy link
Contributor Author

davidsh commented Jun 18, 2019

@Tratcher Can you clarify how ASP.NET Core creates the impersonated context token described by the repro in #29351.

@Tratcher
Copy link
Member

ASP.NET Core does not do any impersonation, a user has to do it manually by calling WindowsIdentity.RunImpersonated. Do you mean how the WindowsIdentity is created? It's created from a handle provided by Http.Sys, IIS, or NTAuthentication.

@davidsh
Copy link
Contributor Author

davidsh commented Jun 18, 2019

Do you mean how the WindowsIdentity is created? It's created from a handle provided by Http.Sys, IIS, or NTAuthentication.

Yes. Thanks for clarifying that ASP.NET Core doesn't control how the identity token handle is created. It is made from http.sys. So,this further reinforces the severity of this issue in that WindowsIdentity.RunImpersonated() is behaving differently on .NET Core compared with .NET Framework. Issue #29351 is being caused as a direct result of this issue.

@Tratcher
Copy link
Member

@brentschmaltz ?

@brentschmaltz
Copy link

@Tratcher wanted to but couldn't get to this. I will have to pick it up after the 4th holiday.

@karelz
Copy link
Member

karelz commented Jul 10, 2019

@brentschmaltz did you get chance to look at it? Are you going to fix it for 3.0? (i.e. in next few days)

@Tratcher is it blocker for 3.0?
cc @danmosemsft

@Tratcher
Copy link
Member

The problems on .NET Core are the same using .NET Core 2.2 or .NET Core 3.0 Preview 6.

Not blocking for 3.0 as this wasn't a regression. It may be a patch candidate for 2.2 (and 3.0) depending on the fix.

@ericstj
Copy link
Member

ericstj commented Aug 27, 2019

Moving to 5.0 since I don't believe this currently meets the bar.

@LeonarddeR
Copy link

I have a similar issue in that exceptions thrown inside the Action supplied to RunImpersonated can't be caught. May be this is related?

@alaitang
Copy link

alaitang commented Jan 6, 2020

Looks like it is working for IP address but not working on hostname.

@ericstj
Copy link
Member

ericstj commented Jan 7, 2020

@davidsh you mentioned that GetAddrInfoExW is making an async call where before we didn't. I wonder if GetAddrInfoExW is sensitive to the impersonation token of the calling thread, and the DNS code isn't flowing the impersonation token to the continuation/EndGetHostAddresses thread? Related: https://github.com/dotnet/corefx/issues/24977. It's not clear to me why we're blaming RunImpersonated for this when we said that we didn't call GetAddrInfoExW on .NETFramework. Is it possible that the implementation of GetHostAddressesAsync
dotnet/corefx#26850 never worked under impersonation? /cc @stephentoub

@msftgits msftgits transferred this issue from dotnet/corefx Feb 1, 2020
@msftgits msftgits added this to the 5.0 milestone Feb 1, 2020
@davidsh
Copy link
Contributor Author

davidsh commented Feb 10, 2020

Is it possible that the implementation of GetHostAddressesAsync
dotnet/corefx#26850 never worked under impersonation?

It's possible but I haven't done any further investigation on this.

@maryamariyan maryamariyan added the untriaged New issue has not been triaged by the area owner label Feb 23, 2020
@glatzert
Copy link

glatzert commented Apr 9, 2020

I'm not 100% positive it's the same issue, but it sounds similar.
We are trying to access an SMB-Share while beeing impersonated and get AccessDenied-Exceptions on the share itself.

Inspecting the problem with ProcMon and WireShark showed, that there is no attempt made to get the S4U-Token in such a way it would be acceptable by the target server.
An older .NET Full application doing something similar shows no such problems.

The older application will make an KRB5 request which fails due to pre-auth and then go on and issue an KRB5 request which proceeds and creates the needed token.
The newer application will do the first KRB5 request and then NOT gather to neccessary token.
Furthermore WireShark does not see any atempt to access the target SMB-share, while ProcMon Shows a failed attempt.
We can probably reduce the application to a minimal repro, if that would help.

@travislaynewilson
Copy link

I second this issue. I'm having to rewrite our .NET Core application back to .NET Framework solely because of this bug - our entire implementation relies on this working.

@travislaynewilson
Copy link

FYI: We removed async from our impersonated calls with no luck.

@davidsh davidsh removed the untriaged New issue has not been triaged by the area owner label May 19, 2020
@davidsh
Copy link
Contributor Author

davidsh commented May 19, 2020

We can probably reduce the application to a minimal repro, if that would help.

Yes, that would be helpful. It would allow us to diagnose it faster.

@glatzert
Copy link

glatzert commented May 29, 2020

Okay I have an application which repros the problem, but the environment needs to be properly configured (btw. running locally is also messy, since it won't load DLLs correctly on non admin accounts).

What do you need:

  1. a Server for running the repro.
  2. A FileServer (Windows Auth Enabled) "\fs01.domain.com"
  3. A gMSA, which is able to delegate to the FileServer, which also has "ActAsPartOfOS" and all necessary privileges
  4. A user account you want to impersonate (user@corp.com)
  5. PsExec to run the repro app as gMSA on the server with the parameters: "\fs01.domain.com" "user@corp.com".

The unexpected error for me was:

System.UnauthorizedAccessException: Access to the path '\fs02.corp.de\username$' is denied.

Impersonation.zip

If you'd like to: my machine is setup and I can access it with you via Teams or something similar.
WireShark et al are also installed, to further investigate and we can set neccessary trace-points, Collectors and what not.

@bartonjs bartonjs modified the milestones: 5.0.0, Future Jul 7, 2020
@wstaelens
Copy link

Windows Authentication in .net core does indead not seem to work as in full framework.
When will we see updates to this? we are porting an app to .net core 3.1 which heavily uses windows impersonation and also uses the policy "act as part of the operating system" so that it can process without having to have all passwords, but for now we are forced to enter all passwords...

@brentschmaltz
Copy link

@bartonjs do you know what the root cause is?

@bartonjs
Copy link
Member

@brentschmaltz I don't, no. But I don't know anything about impersonation. I believe that the hope is that you would tell us 😄.

@wfurt wfurt reopened this Jan 12, 2021
@wfurt wfurt modified the milestones: 6.0.0, 5.0.x Jan 12, 2021
@samsp-msft
Copy link
Member

This issue is blocking a number of customers from moving to core. Is it possible to backport to 3.1 as that is the LTS branch?

@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Jan 13, 2021
@wfurt
Copy link
Member

wfurt commented Jan 13, 2021

I submitted fix to 6.0 branch. It would be great if interested users can give it try and provide feedback: https://github.com/dotnet/installer
I'm trying to get permission to get fix ported to servicing.

@karelz
Copy link
Member

karelz commented Jan 13, 2021

@danmosemsft what is the process & bar for backporting to 3.1?
@samsp-msft can you please share more details on the business need to backport to 3.1? Why is 5.0 not sufficient in these cases?

@danmoseley
Copy link
Member

@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.

@danmoseley
Copy link
Member

They would strongly prefer that we have a customer who has tried out the privates though.

@samsp-msft
Copy link
Member

The reason to back port to 3.1 is that is the LTS version, and many customers need to be on the version that will get them long term support.

I will reach out by email to the customer.

@danmoseley
Copy link
Member

It would be good to understand whether it is important to get it out quickly, as well. If February is not necessary, you have an extra month for a customer to validate the change.

@karelz
Copy link
Member

karelz commented Jan 13, 2021

I would rather wait until we get validation of privates -- @wfurt what do you think?

@wfurt
Copy link
Member

wfurt commented Jan 13, 2021

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.

@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Jan 14, 2021
@wfurt
Copy link
Member

wfurt commented Feb 2, 2021

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).

@wfurt wfurt closed this as completed Feb 2, 2021
@som-nitjsr
Copy link

som-nitjsr commented Feb 10, 2021

I have taken the release 5.0.3 and i am trying to run this code in IIS with Network service account. and Network service account has access on shared location.

i am getting this error NT AUTHORITY\NETWORK SERVICE Could not find a part of the path 'z:\a.txt'.

Am i Missing some thing? i though now it should work.

[HttpGet]
        public string Get()
        {
           
            WindowsIdentity current = WindowsIdentity.GetCurrent();
          string message = current.Name;
            try
            {
                WindowsIdentity.RunImpersonated(current.AccessToken, new Action(() => message+= System.IO.File.ReadAllText("z:\\a.txt")));
            }
            catch (Exception exception)
            {
                message+=exception.Message;
            }
            return message;
        }

image

@karelz
Copy link
Member

karelz commented Feb 10, 2021

@som-nitjsr this bug fix was about DNS not working. Your code seems to be unrelated. Or am I missing something?

@som-nitjsr
Copy link

I am good now this code in working by using UNC path.

@som-nitjsr
Copy link

som-nitjsr commented Feb 13, 2021

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).

Hi @wfurt
I have tested this, and it is working in .net 5.0.3 by when it will be available in .net core 3.1

@wfurt
Copy link
Member

wfurt commented Feb 13, 2021

there is no plan for 3.1 right now AFAIK @som-nitjsr. You can try to talk to @karelz and @samsp-msft about business impact.

@Bonsai11
Copy link

Does anyone know since when this has been broken? I recently moved from 2.1 to 3.1 and now realised this is not working correctly. I'm not sure if this was working on 2.1 and I can't go back so easily just to test this. I remember successfully trying this longer ago on 2.1 but I might have been using an IP address and therefore unknowingly avoiding this problem.

@stephentoub
Copy link
Member

Does anyone know since when this has been broken?

Since (including) 2.1.

@Bonsai11
Copy link

Bonsai11 commented Mar 4, 2021

Are there any news about 3.1? Is there maybe any workaround for this at least?
Would really need this to be working.

@karelz
Copy link
Member

karelz commented Mar 4, 2021

@Bonsai11 the easiest workaround is to use .NET 5.0.3+ ... or I wonder if you can go back to using IP which worked for you on 2.1 as you mentioned above.

If you need 3.1 backport, we would need business justification - how much impact is it on your service/app (number of customers impacted, size of the service, importance of the scenario, etc.) and why 5.0 is not an option for you.

@ghost ghost locked as resolved and limited conversation to collaborators Apr 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Net tenet-compatibility Incompatibility with previous versions or .NET Framework
Projects
None yet
Development

Successfully merging a pull request may close this issue.