2

We have some legacy web application code which we are updating and porting into a .NET 4.0 runtime.

The code is in a class library and connects to a named pipe endpoint using WCF.

When I initiate the connection from a console application, everything works fine.

When I initiate the connection from a web application, I receive an exception:

Access is denied
Server stack trace:
  at System.ServiceModel.Channels.AppContainerInfo.GetCurrentProcessToken()
  at System.ServiceModel.Channels.AppContainerInfo.RunningInAppContainer()
  at System.ServiceModel.Channels.AppContainerInfo.get_IsRunningInAppContainer()
  at System.ServiceModel.Channels.PipeSharedMemory.BuildPipeName(String pipeGuid)
  at System.ServiceModel.Channels.PipeSharedMemory.get_PipeName()
  at System.ServiceModel.Channels.PipeConnectionInitiator.GetPipeName(Uri uri, IPipeTransportFact… Object[] , Object[] )
  at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
  at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
  at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
  at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc& rpc)
  at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)

The error originates at a boundary between the managed code and unmanaged code where there is a call into advapi32.dll:

[SecurityCritical]
private static SafeCloseHandle GetCurrentProcessToken()
{
  SafeCloseHandle TokenHandle = (SafeCloseHandle) null;
  if (!UnsafeNativeMethods.OpenProcessToken(UnsafeNativeMethods.GetCurrentProcess(), TokenAccessLevels.Query, out TokenHandle))
    throw System.ServiceModel.FxTrace.Exception.AsError((Exception) new Win32Exception(Marshal.GetLastWin32Error()));
  return TokenHandle;
}

[DllImport("advapi32.dll", SetLastError = true)]
internal static extern bool OpenProcessToken(IntPtr ProcessHandle, TokenAccessLevels DesiredAccess, out SafeCloseHandle TokenHandle);

Various topics on the web suggest removing the element or setting impersonate="false":

<system.web>
  <identity impersonate="true"/>
</system.web>

And indeed, this works to fix my issue. However, I'm not certain what side effects this may have on the application (SharePoint 2016) so I'm reluctant to simply remove this attribute.

The SecurityCritical attribute gave me some hints in that perhaps this is related to the change in the CAS model between .NET 2.0 and .NET 4.0. The code is installed into the GAC so it should be running under full trust already but I gave it a shot anyways.

I have also tried adding [SecuritySafeCritical] to the method and the class which invokes the IChannel.Open() to no avail.

I also tried adding [assembly: SecurityRules(SecurityRuleSet.Level1)] on the assembly as this should lock into the .NET Framework 2.0 security rules.

I'm looking for any additional insight and other methods to try to resolve this issue.

There is some similarity to this other Stack post: How to call net.pipe (named pipe) WCF services while impersonating in a Windows Service except that there is no explicit impersonation occurring so I'm not certain that the fix would apply.

An additional note is that when I try to call System.Diagnostics.Process.GetCurrentProcess(), the same error is thrown. The error also originates when trying to get a handle on the current executing process.

Charles Chen
  • 1,395
  • 13
  • 22
  • Is the named pipe WCF service hosted in IIS? Do you have the ability to host it another way (eg. self-hosted or a Windows Service)? – lesscode May 06 '18 at 20:48
  • @lesscode it is hosted in IIS with WAS activation. However, as I mentioned, the error is not originating from the service side; it fails on the client side when opening the channel. (The client is also in IIS). If I run the client from a console app, it works fine. – Charles Chen May 06 '18 at 20:57
  • My guess is that your console application gets to run elevated, and your Sharepoint web app does not. I _think_ (my WCF is a little rusty) that WCF named pipes can only be accessed in the same logon session as the interactive user, unless the process can create a global (and not a local) kernel object for resolving pipe names. I'll see if I can dig up some more detailed notes on this from previous lives... – lesscode May 06 '18 at 21:10
  • The odd thing is that the app pool account is an admin account and the Windows account I'm using to connect is also an admin account. When I debug through the code, the `HttpContext` user is reported as the admin account. In the previous 3.5 version (.NET 2.0 runtime), this worked fine (perhaps some configuration difference, which I have not yet found?) – Charles Chen May 06 '18 at 21:14
  • Ah, I also found this which rang a few bells: https://stackoverflow.com/questions/3366976/using-wcfs-net-pipe-in-a-website-with-impersonate-true – lesscode May 06 '18 at 21:37
  • I checked that link and the failure point seems to be different. I even used Wayback Machine to view the referenced pages by Chris Dickson. The failure in the scenario documented by Chris seems related to the ACLs on the named pipe. However, I believe that the issue I'm running into is occurring before it gets to accessing the named pipe; it is actually occurring when resolving the *address* of the named pipe; it cannot make the necessary call into `advapi32.dll`. I tried using `OpenProcessToken` to get the process token to impersonate and got the same error. – Charles Chen May 06 '18 at 23:26
  • Maybe that's the shared kernel object that you can't access (for the same reason)? Although that seems unlikely since the stack is complaining about a process token. About out of ideas. It doesn't look like Chris is very active here any more - I bet he would have more insight. – lesscode May 07 '18 at 01:59
  • Not sure if you came across this in your searching: https://social.msdn.microsoft.com/Forums/vstudio/en-US/6a26497f-0346-4929-ad42-ff4459be60e4/error-while-attempting-to-call-netpipe-wcf-service-while-impersonating?forum=wcf – lesscode May 07 '18 at 02:04

1 Answers1

0

I've concluded that this issues is related to the internals of System.ServiceModel in .NET 4.0.

Initially, I thought that this may be related to Server 2016 UAC or .NET 4.0/IIS 10 web application runtime settings (e.g. .NET 2.0 vs .NET 4.0 CAS models). I created a simple web application in .NET 3.5 and tried to call Process.GetCurrentProcess().Handle. I ran this in the new server and it failed with the same "Access is denied" error.

I took this into the old server (Windows Server 2008 R2, .NET 3.5) and ran it there expecting this to work and lo-and-behold, it also fails. So I browsed the source for System.ServiceModel in 3.5 and found that there is no AppContainerInfo and thus it is likely that the 3.5 code does not make the same Win32 API level calls at all.

My conclusion is that we did not encounter this error before because the old 3.0 libraries did not need to invoke APIs from advapi32.dll or had some other mechanism to create the pipe name.

Indeed, here is the first few lines of the implementation from PipeConnectionInitiator.GetPipeName in 3.0":

internal static string GetPipeName(Uri uri)
{
  string[] strArray = new string[3]
  {
    "+",
    uri.Host,
    "*"
  };
  bool[] flagArray = new bool[2]{ true, false };
  for (int index1 = 0; index1 < strArray.Length; ++index1)
  {
    for (int index2 = 0; index2 < flagArray.Length; ++index2)
    {

And here is the first few lines in 4.0:

internal static string GetPipeName(Uri uri, IPipeTransportFactorySettings transportFactorySettings)
{
  AppContainerInfo appContainerInfo = PipeConnectionInitiator.GetAppContainerInfo(transportFactorySettings);
  string[] strArray = new string[3]
  {
    "+",
    uri.Host,
    "*"
  };
  bool[] flagArray = new bool[2]{ true, false };
  string str1 = string.Empty;
  string str2 = (string) null;
  for (int index1 = 0; index1 < strArray.Length; ++index1)
  {
    for (int index2 = 0; index2 < flagArray.Length; ++index2)
    {
      if (appContainerInfo == null || !flagArray[index2])

So the 4.0 implementation requires access to execute OpenProcessToken.

One option, if the code is sufficiently isolated, is to use an assembly binding redirect:

<runtime>
  <assemblyBinding>
    <dependentAssembly>
      <assemblyIdentity name="System.ServiceProcess" publicKeyToken="b77a5c561934e089" culture="neutral" />
      <bindingRedirect oldVersion="4.0.0.0" newVersion="3.0.0.0" />
    </dependentAssembly>            
  </assemblyBinding>
</runtime>

And simply force the runtime to bind to the old versions.

Unfortunately, the application has some dependencies on System.ServiceModel 4.0 so it's not so simple for me to switch.

Charles Chen
  • 1,395
  • 13
  • 22