Charteris Community Server

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

Chris Dickson's Blog

Named pipe communication with low integrity clients

The release of Windows Vista introduced a completely new dimension to the Windows operating system’s security model for determining what code executing in user mode can do. In addition to the concepts of access rights, and system privileges, user mode processes are now also constrained by the “integrity level” with which they are associated. This integrity level is determined by a special Access Control Entry (ACE) called the Mandatory Integrity Label, added to the System Access Control List (SACL) of the process token, or implied as the default integrity level if no explicit mandatory label ACE is present. Most user mode process tokens do not have an integrity label and are therefore given the default integrity level of “Medium”; processes started using “Run as Administrator…” (or in an administrator’s interactive login session) have “High” integrity level. Services running as Local System have an even higher integrity level: “System”. Internet Explorer always runs in a Low integrity process.

What difference does this make? Well, the key point of this mechanism is that all securable objects also have an associated integrity level, and write access to such objects is constrained by the operating system so that regardless of the discretionary access rights in the object’s access control list, no process is allowed to perform a write operation on an object with a higher integrity level than its own access token.

My interest in this was sparked by enquiries from people who needed inter-process communication between a low integrity process (invariably IE hosting their code in some way) and another custom process such as a windows service or a tray applet, and were hoping to use WCF with the named pipe binding to implement this. Such a scheme worked fine on Windows XP but broke when moved to Vista or Windows 7. This is because named pipes are securable objects provided by the operating system kernel, and the pipes created by the WCF binding have no integrity label. They are therefore implicitly treated by default as having medium integrity level and are consequently inaccessible to low integrity processes regardless of what access permissions the DACL grants.

Not one to be deterred by apparent constraints in WCF’s implementation, I set about investigating whether and how this scenario could be supported. The good news is that I found a way, which I will explain over the course of a few posts. The bad news is that it involves some fairly hairy use of reflection over the WCF types, so Microsoft support won’t be too enthusiastic about helping if it’s used in production, and it will be at the mercy of changes to implementation details in future .NET versions and service packs.

The first step is to fill in the gaps in the .NET library types as regards controlling the mandatory integrity label associated with a securable object. The .NET security types for handling security descriptors, access control lists and access control entries are entirely ignorant of the Mandatory Integrity Control mechanism introduced by Windows Vista, so we need to P/Invoke Win32 APIs directly in order to do anything with integrity labels. My solution is a class, IntegrityLabel, which can be used like this:

   1: // Read the current integrity label of a NamedPipeServerStream's pipe, if it has one
   2: IntegrityLabel currentLabel = IntegrityLabel.GetCurrentIntegrityLabelForResource(
   3:                          pipe.SafePipeHandle, 
   4:                          ResourceType.KernelObject);
   5: Console.WriteLine(
   6:    null != currentLabel ? 
   7:    String.Format("Pipe MIC label: {0}", currentLabel.Sddl) : 
   8:    "Pipe has no MIC label");
   9:  
  10: // Output is "S:(ML;;NW;;;LW)" if pipe is marked low integrity

or this:
   1: // Add a Low Integrity mandatory label to a named pipe's security descriptor
   2: // (must be a handle to the first server pipe instance)
   3: IntegrityLabel lbl = new IntegrityLabel(IntegrityLabel.IntegrityLevel.Low);
   4: lbl.ApplyToResource(pipe.SafePipeHandle, ResourceType.KernelObject);
   5:  
   6: // This enables low integrity processes to write to the pipe (if the pipe DACL allows).
   7: // Be sure to validate all data arriving via the pipe very carefully.

I am not going to post the entire code for my IntegrityLabel, as it is still work-in-progress for the moment, but I will sketch out how I have implemented it.

Reading the label and instantiating IntegrityLabel from it, as in the first example, involves a call to the Win32 API GetSecurityInfo, specifying the new value LABEL_SECURITY_INFORMATION (0x00000010) for the SECURITY_INFORMATION flag. The SACL returned by the function is then copied and parsed. While writing this I found that there are some public classes in the namespace System.Security.AccessControl which can be leveraged to avoid too much hand-parsing of the security structures. For example, I have a constructor like this which consumes the binary SACL obtained from the Win32 call:

   1: public IntegrityLabel(byte[] saclBinaryForm, int offset)
   2: {
   3:     GenericAce ace = null;
   4:     if (null != saclBinaryForm)
   5:     {
   6:         RawAcl aclRaw = new RawAcl(saclBinaryForm, 0);
   7:         if (0 >= aclRaw.Count) throw new ArgumentException("No ACEs in ACL", "saclBinaryForm");
   8:         ace = aclRaw[0];
   9:         if (Win32.SYSTEM_MANDATORY_LABEL_ACE_TYPE != (int)ace.AceType) 
  10:             throw new ArgumentException("No Mandatory Integrity Label in ACL", "saclBinaryForm");
  11:         byte[] aceBytes = new byte[ace.BinaryLength];
  12:         ace.GetBinaryForm(aceBytes, 0);
  13:         _policy = new IntegrityPolicy(aceBytes, 4);
  14:         _level = new IntegrityLevel(aceBytes, 8);
  15:         return;
  16:     }
  17:     throw new ArgumentNullException("saclBinaryForm");
  18: }

The type System.Security.AccessControl.RawAcl saves me the trouble of boilerplate parsing of the ACL structure’s header, and gives me an instance of System.Security.AccessControl.GenericAce which ensures that I don’t mess up the extraction of the access control entry (ACE) structure which holds the integrity label. This GenericAce object is actually of type CustomAce, a type is specifically designed to support extending the existing System.Security.AccessControl types to new ACE types not defined when this was written, in this case the SYSTEM_MANDATORY_LABEL_ACE structure. CustomAce exposes the basic details of the ACE header but can’t help with the specifics of the SYSTEM_MANDATORY_LABEL_ACE structure, so I am on my own again to pick out the integrity policy mask (parsed in the constructor of my nested IntegrityPolicy class) and to find the start of the SID which defines the integrity level to which the ACE relates.

Here is the IntegrityPolicy constructor used when reading the binary form obtained from the Win32 API: it just interprets the integrity policy bitmask. Internally I store the policy in its SDDL string form:

   1: internal IntegrityPolicy(byte[] binaryForm, int offset)
   2: {
   3:     int accessMask = BitConverter.ToInt32(binaryForm, offset);
   4:     StringBuilder sb = new StringBuilder(4);
   5:     if (0 != (accessMask & Win32.SYSTEM_MANDATORY_LABEL_NO_WRITE_UP)) sb.Append("NW");
   6:     if (0 != (accessMask & Win32.SYSTEM_MANDATORY_LABEL_NO_READ_UP)) sb.Append("NR");
   7:     if (0 != (accessMask & Win32.SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP)) sb.Append("NX");
   8:     _sddl = sb.ToString();
   9: }

Similarly, the IntegrityLevel constructor which interprets the binary form data. This time it is one of the SIDs which define the integrity levels recognised by the MIC mechanism, and the System.Security.Principal.SecurityIdentifer framework type provides the necessary parsing and interpretation. Again, I store the level in the form it appears in SDDL strings:

   1: internal IntegrityLevel(byte[] binaryForm, int offset)
   2: {
   3:     SecurityIdentifier sid = new SecurityIdentifier(binaryForm, offset);
   4:     if ("S-1-16-4096".Equals(sid.Value))
   5:     {
   6:         _sddl = "LW";
   7:     }
   8:     else if ("S-1-16-8192".Equals(sid.Value))
   9:     {
  10:         _sddl = "ME";
  11:     }
  12:     else if ("S-1-16-12288".Equals(sid.Value))
  13:     {
  14:         _sddl = "HI";
  15:     }
  16:     else if ("S-1-16-16384".Equals(sid.Value))
  17:     {
  18:         _sddl = "SI";
  19:     }
  20:     else throw new ArgumentException(String.Format("Invalid SID for integrity level: {0}", sid.Value));
  21: }

We now turn to the other use case, writing an integrity label to the SACL of an object. The key Win32 API here is SetSecurityInfo:

   1: public unsafe void ApplyToResource(SafeHandle handle, ResourceType objectType)
   2: {
   3:     fixed (byte* pSacl = GetSaclBinaryForm())
   4:     {
   5:         int result = Win32.SetSecurityInfo(
   6:                 handle,
   7:                 ResourceType.KernelObject,
   8:                 Win32.LABEL_SECURITY_INFORMATION,
   9:                 null, null, null, pSacl);
  10:         if (0 != result)
  11:         {
  12:             throw new Win32Exception(result);
  13:         }
  14:     }
  15: }

Currently I am implementing GetSaclBinaryForm by round-tripping the SDDL representation of the label via the Win32 APIs ConvertStringSecurityDescriptorToSecurityDescriptor and GetSecurityDescriptorSacl, but I may change everything to use the binary representation internally. Anyway, the key here is that the SACL structure provided to SetSecurityInfo should contain just the required MIC label.

Armed with my IntegrityLabel type, it takes just the two lines of code shown above to lower the integrity level of a resource such as a named pipe. In order for the call to succeed, the handle supplied to IntegrityLabel.ApplyToResource() must have been opened with WRITE_OWNER permission enabled. In the case of named pipe objects, this requirement introduces a small gotcha: in the CreateNamedPipe API, the required access permissions for the pipe handle are specified in the dwOpenMode argument, and this API overloads the meaning of the WRITE_OWNER bit flag 0x80000, to mean FILE_FLAG_FIRST_PIPE_INSTANCE as well as WRITE_OWNER. It has a certain logic to it, certainly, but the upshot is that calling SetSecurityInfo to specify a MIC integrity label can only ever succeed using a handle to the first server instance of a named pipe, and not a handle to a subsequent instance.

Next time we’ll look at using this knowledge, together with the IntegrityLabel class, to locate and lower the integrity of the pipe created by the WCF NetNamedPipeBinding.

 

Published Feb 15 2011, 01:21 PM by chrisdi
Filed under: ,

Comments

No Comments

Leave a Comment

(required) 
(optional)
(required) 
Submit
Powered by Community Server (Commercial Edition), by Telligent Systems