Charteris Community Server

Welcome to the Charteris plc Community
Welcome to Charteris Community Server Sign in | Join | Help
in Search

Chris Dickson's Blog

Enabling WCF named pipe endpoint for low integrity clients

My previous post looked at the Mandatory Integrity Control mechanism and what it means for named pipe communication in general. Now I turn to dealing with this issue in WCF. The scenario I particularly want to support is a WCF service hosted in a Windows Service process, providing service over a named pipe transport to client code hosted in an Internet Explorer low integrity process on the same machine.

We’ll do this with a custom binding element that we can add to the standard NetNamedPipeBinding elements on the service side. This will hold an instance of the IntegrityLabel class described in my last post, representing the Mandatory Integrity label we want to specify for the pipe. Its job as a binding element will be to grab the channel listener just after it has been opened, find the pipe handle on which the service is listening, and apply the integrity label to its SACL. Here’s the basic framework:

   1: public class NetNamedPipeSecurityExtensionsBindingElement : BindingElement
   2: {
   3:     IntegrityLabel _pipeMandatoryIntegrityLabel;
   5:     public NetNamedPipeSecurityExtensionsBindingElement(
   6:                           IntegrityLabel pipeIntegrityLabelRequired)
   7:     {
   8:         _pipeMandatoryIntegrityLabel = new IntegrityLabel(pipeIntegrityLabelRequired.Level, 
   9:                                                           pipeIntegrityLabelRequired.Policy);
  10:     }
  12:     public override BindingElement Clone()
  13:     {
  14:         return new NetNamedPipeSecurityExtensionsBindingElement(_pipeMandatoryIntegrityLabel);
  15:     }
  17:     public override T GetProperty<T>(BindingContext context)
  18:     {
  19:         if (typeof(T) == typeof(IntegrityLabel))
  20:         {
  21:             return new IntegrityLabel(_pipeMandatoryIntegrityLabel.Level, 
  22:                                       _pipeMandatoryIntegrityLabel.Policy) as T;
  23:         }
  24:         return context.GetInnerProperty<T>();
  25:     }
  27:     public override IChannelListener<TChannel> BuildChannelListener<TChannel>(
  28:                                                             BindingContext context)
  29:     {
  30:         IChannelListener<TChannel> listener = context.BuildInnerChannelListener<TChannel>();
  31:         listener.Opened += new EventHandler(OnChannelListenerOpened);
  32:         return listener;
  33:     }
  35:     void OnChannelListenerOpened(object sender, EventArgs e)
  36:     {
  37:         ChannelListenerBase listener = sender as ChannelListenerBase;
  38:         if (null != listener)
  39:         {
  40:            // Find a suitable pipe handle and apply the integrity label
  41:         }
  42:     }
  43: }

The difficult part is finding the pipe handle, as it is not held by the channel listener. The channel listener can be though of as a listener for logical channels arriving at an endpoint: it is not directly concerned with the physical details of the transport layer. One reason for this is that on a connection-oriented transport like Named Pipes or TCP/IP, there is not a direct one-to-one correspondence between physical connections and channels – connections may be pooled and reused. So, in addition to the channel listener, there is also a transport connection listener. The two are connected indirectly via an internal WCF abstraction called a TransportManager. The relationship is illustrated in the following diagram.

NetNamedPipe - Server-Side Runtime

As I have noted in the diagram, there are two different TransportManager types used for Named Pipes in WCF. Up to now I have focussed solely on the one used for self-hosted services. The hosted transport manager is rather more obscure to understand, and I have not yet tackled that.

For the self-hosted scenario, in order to find the pipe handle, we need to:

  • locate the transport manager table: for the Named Pipe transport, this is a singleton held in a static field of one of the base classes for the channel listener.
  • look up in the transport manager table the appropriate transport manager for the URI on which our endpoint is listening: in our self-hosted scenario this will be an instance of ExclusiveNamedPipeTransportManager.
  • find the IConnectionListener object stored in a field of the TransportManager: this may not be the actual transport connection listener, but could be some other type wrapping the inner connection listener (for instance, a wrapper to implement tracing when WCF tracing is enabled).
  • to get the actual PipeConnectionListener instance, we need to walk down the chain of IConnectionListener instances, in “Russian Doll” fashion, until we find the one we are after.
  • Enumerate the PendingAccept list associated with the PipeConnectionListener. If we are not too unlucky, we should find that one of these contains the first pipe handle created when the channel listener was opened, and we can use this to apply the integrity label.

All this involves dealing extensively with WCF internal types, and in several cases with private member fields thereof. So it all has to be done using reflection and so comes with a big health warning that WCF service packs may come along and break everything. For this reason I wrap the reflection code in a series of Wrapper types which mirror the internal WCF types, and at every step in my actual code I validate carefully that everything looks as I expect it to – though I have omitted much of the error checking in the extracts I show here, just to make it more readable.

First I have a wrapper for the TransportManagerTable, which encapsulates the logic of finding the table and looking up the appropriate TransportManager for the endpoint. The lookup is complicated by the fact that the table uses an internal WCF type, BaseUriWithWildcard, to key its entries. This type is based on the endpoint URI, but also takes into account the HostNameComparisonMode configured for the binding. In order to perform the lookup we need to construct an instance of this key type using reflection to invoke the appropriate internal constructor. 

   1: public class TransportManagerTableWrapper
   2: {
   3:     private object _transportManagerTable;
   4:     private IList _tableContents;
   5:     private PropertyInfo _contentKeyProperty;
   6:     private PropertyInfo _contentValueProperty;
   7:     private Type _contentKeyType;
   8:     private HostNameComparisonMode _hostNameComparisonMode;
  10:     public TransportManagerTableWrapper(ChannelListenerBase channelListener)
  11:     {
  12:         PropertyInfo pi = channelListener.GetType().GetProperty("TransportManagerTable", 
  13:             BindingFlags.Instance | BindingFlags.NonPublic);
  14:         _transportManagerTable = pi.GetValue(channelListener, null);
  15:         _tableContents = _transportManagerTable.GetType().GetMethod("GetAll").Invoke(
  16:                     _transportManagerTable, null) as IList;
  17:         Type tableContentsItemType = _tableContents[0].GetType();
  18:         var contentItemPairTypes = tableContentsItemType.GetGenericArguments();
  19:         _contentKeyProperty = tableContentsItemType.GetProperty("Key");
  20:         _contentValueProperty = tableContentsItemType.GetProperty("Value");
  21:         _contentKeyType = contentItemPairTypes[0];
  22:         PropertyInfo hncmProperty = channelListener.GetType().GetProperty(
  23:                     "HostNameComparisonMode", BindingFlags.Instance|BindingFlags.Public);
  24:         _hostNameComparisonMode = (HostNameComparisonMode)hncmProperty.GetValue(
  25:                     channelListener, null);
  26:     }
  28:     public TransportManagerWrapper GetTransportManager(Uri listenUri)
  29:     {
  30:         object keyBaseUriWithWildcard = CreateBaseUriWithWildcard(listenUri);
  31:         object transportManager = null;
  32:         foreach (object kvPair in _tableContents)
  33:         {
  34:             if (_contentKeyProperty.GetValue(kvPair, null).Equals(keyBaseUriWithWildcard))
  35:             {
  36:                 transportManager = _contentValueProperty.GetValue(kvPair, null);
  37:                 break;
  38:             }
  39:         }
  40:         if (listenUri.Scheme == Uri.UriSchemeNetPipe)
  41:         {
  42:             return new NamedPipeTransportManagerWrapper(transportManager);
  43:         }
  44:         return new TransportManagerWrapper(transportManager);
  45:     }
  47:     private object CreateBaseUriWithWildcard(Uri listenUri)
  48:     {
  49:         BindingFlags ctorBindingFlags = BindingFlags.Instance;
  50:         ctorBindingFlags |= (Environment.Version.Major >= 4) ? 
  51:             BindingFlags.Public : BindingFlags.NonPublic;
  52:         ConstructorInfo ctor = _contentKeyType.GetConstructor(
  53:             ctorBindingFlags, null, 
  54:             new Type[] { typeof(Uri), typeof(HostNameComparisonMode) }, 
  55:             null);         
  56:         return ctor.Invoke(new object[]{listenUri, _hostNameComparisonMode});
  57:     }
  58: }

The NamedPipeTransportManagerWrapper type returned by the GetTransportManager method shown above encapsulates the logic for the next stage – walking the chain of IConnectionListener types which it encloses. Here is the key method (again note that required error checking is missing from this sample code):

   1: public PipeConnectionListenerWrapper PipeConnectionListener
   2: {
   3:     get
   4:     {
   5:         FieldInfo connectionListenerFieldInfo = WrappedType.GetField("connectionListener", 
   6:             BindingFlags.Instance | BindingFlags.NonPublic);
   7:         if (null != connectionListenerFieldInfo)
   8:         {
   9:             object connectionListener = connectionListenerFieldInfo.GetValue(
  10:                                                                       _transportManager);
  12:             // connectionListener may be wrapping another IConnectionListener type
  13:             // we need the innermost IConnectionListener
  14:             int recursionCountLeft = 10;
  15:             while (recursionCountLeft > 0 
  16:                    && null == connectionListener.GetType().GetField(
  17:                                           "pendingAccepts", 
  18:                                           BindingFlags.Instance | BindingFlags.NonPublic))
  19:             {
  20:                 recursionCountLeft--;
  21:                 FieldInfo[] fields = connectionListener.GetType().GetFields(
  22:                                           BindingFlags.Instance | BindingFlags.NonPublic);
  23:                 foreach (FieldInfo f in fields)
  24:                 {
  25:                     if ("IConnectionListener".Equals(f.FieldType.Name))
  26:                     {
  27:                         connectionListener = f.GetValue(connectionListener);
  28:                         break;
  29:                     }
  30:                 }
  31:             }
  32:             return new PipeConnectionListenerWrapper(connectionListener);
  33:         }
  34:         else
  35:         {
  36:             // unsupported transport manager type
  37:             // e.g. HostedNamedPipeTransportManager
  38:             throw ...
  39:         }
  40:     }
  41: }

Finally, we have a PipeConnectionListenerWrapper, which encapsulates an iterator for the pipe handles held by PendingAccept instances, within the connection listener:

   1: public IEnumerable<SafeHandle> PendingPipes
   2: {
   3:     get 
   4:     {
   5:         IList pendingAccepts = null;
   6:         FieldInfo pendingAcceptField = _wrappedType.GetField("pendingAccepts", 
   7:             BindingFlags.Instance | BindingFlags.NonPublic);
   8:         if (null != pendingAcceptField)
   9:         {
  10:             pendingAccepts = pendingAcceptField.GetValue(_pipeConnectionListener) as IList;
  11:         }
  12:         foreach (object pendingAccept in pendingAccepts)
  13:         {
  14:             var pipeHandle = pendingAccept.GetType().GetField("pipeHandle", 
  15:                 BindingFlags.Instance|BindingFlags.NonPublic).GetValue(pendingAccept);
  16:             yield return pipeHandle as SafeHandle;
  17:         }
  18:     }
  19: }

Using these helper classes we can now complete the implementation of our binding element class, by doing the following in the OnChannelListenerOpened event handler:

   1: TransportManagerTableWrapper transportManagerTable = 
   2:                                  new TransportManagerTableWrapper(listener);
   3: NamedPipeTransportManagerWrapper transportManager = 
   4:                     transportManagerTable.GetTransportManager(listener.Uri) 
   5:                     as NamedPipeTransportManagerWrapper; 
   6: PipeConnectionListenerWrapper pipeConnectionListener = 
   7:                                  transportManager.PipeConnectionListener;
   8: bool successfullyAppliedLabel = false;
   9: List<Win32Exception> exceptions = new List<Win32Exception>();
  10: foreach (SafePipeHandle pipeHandle in pipeConnectionListener.PendingPipes)
  11: {
  12:     try
  13:     {
  14:         _pipeMandatoryIntegrityLabel.ApplyToResource(pipeHandle);
  15:         successfullyAppliedLabel = true;
  16:         break;
  17:     }
  18:     catch (Win32Exception exception)
  19:     {
  20:         exceptions.Add(exception);
  21:     }
  22: }
  24: // ... do something with errors if we didn't succeed in setting the MI label

It all seems to work, based on the testing I have done to date, at least to support the scenario I outlined in the introduction above. I hope someone else may find this useful. Any feedback would be most welcome.

EDITED 8-3-11: Minor change to TransportManagerTableWrapper to support .NET 4.0 as well as .NET 3.5

Published Mar 08 2011, 04:54 PM by chrisdi
Filed under: , ,



Zulfiqar said:

This is all unsupported as you are relying on private reflection?

The implementation details of NetNamedPipeBinding can change at anytime (with an SP or new version of WCF) beaking this code.

March 9, 2011 9:22 AM

chrisdi said:

@Zulfiqar: Yes, indeed - as I pointed out very clearly in the health warning in the post.

That's something one would have to take into account, weighing risks against benefits, before deciding to use this approach. The alternative approach in WCF would be to write an entirely new transport binding element for a named pipe transport, providing control over the integrity label of the pipe - and you won't get Microsoft support for a custom transport binding element either.

March 9, 2011 9:50 AM

jeff said:

Chris, Thank you for all of the good posts you have been doing on the subject of WCF and named pipes. If you happen to have a chance could you peek at the problem I've having that I posted on the MSDN forums to see if you might be able to point me in the right direction?

April 29, 2011 4:56 AM

Leave a Comment

Powered by Community Server (Commercial Edition), by Telligent Systems