17

I'm working on an application which is made of two modules. These modules communicate through named pipes in the following environment:

  • Windows 7 Home Premium x64
  • Visual Studio 2008
  • C# / .Net 3.5

The server runs with administrator rights (high integrity level). The client runs in low integrity level. So that the client can connect to the server, I need to create the pipe in low integrity level. I manage to do this only when the server runs in medium integrity level.

I tested the following setups :

  1. server : high, client : low => access refused
  2. server : high, client : medium => access refused
  3. server : high, client : high => OK
  4. server : medium, client : low => OK
  5. server : medium, client : medium => OK
  6. server : low, client : low => OK

Setup #4 shows that the named pipe gets created with different integrity level than the one of the process, which is good. However, the setup I am interested in is the first one.

I have a sample which makes it easy to test. If the connection is successful, the clients writes "Connected" and the server writes "Received connection". If the connection fails, the client writes "Failed" and the server stays on "Waiting".

Here is how I execute the client program (for the server, simply replace NamePipeClient with NamedPipeServer):

  • medium integrity level:
    • open the command prompt
    • icacls NamedPipeClient.exe /setintegritylevel Medium

    • NamedPipeClient.exe

  • low integrity level:
    • open the command prompt
    • icacls NamedPipeClient.exe /setintegritylevel Low

    • NamedPipeClient.exe

  • high integrity level:
    • open the command prompt in admin mode
    • icacls NamedPipeClient.exe /setintegritylevel High

    • NamedPipeClient.exe

Any help will be greatly appreciated!

Server code

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Win32.SafeHandles;
using System.IO.Pipes;

namespace NamedPipeServer
{
    class Program
    {
        static void Main(string[] args)
        {
            SafePipeHandle handle = LowIntegrityPipeFactory.CreateLowIntegrityNamedPipe("NamedPipe/Test");
            NamedPipeServerStream pipeServer = new NamedPipeServerStream(PipeDirection.InOut, true, false, handle);
            pipeServer.BeginWaitForConnection(HandleConnection, pipeServer);

            Console.WriteLine("Waiting...");
            Console.ReadLine();
        }

        private static void HandleConnection(IAsyncResult ar)
        {
            Console.WriteLine("Received connection");
        }
    }
}

LowIntegrityPipeFactory.cs

using System;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.IO.Pipes;
using System.ComponentModel;
using System.IO;
using System.Security.Principal;
using System.Security.AccessControl;

namespace NamedPipeServer
{
    static class LowIntegrityPipeFactory
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern SafePipeHandle CreateNamedPipe(string pipeName, int openMode,
            int pipeMode, int maxInstances, int outBufferSize, int inBufferSize, int defaultTimeout,
            SECURITY_ATTRIBUTES securityAttributes);

        [DllImport("Advapi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = false)]
        private static extern bool ConvertStringSecurityDescriptorToSecurityDescriptor(
            [In] string StringSecurityDescriptor,
            [In] uint StringSDRevision,
            [Out] out IntPtr SecurityDescriptor,
            [Out] out int SecurityDescriptorSize
        );

        [StructLayout(LayoutKind.Sequential)]
        private struct SECURITY_ATTRIBUTES
        {
            public int nLength;
            public IntPtr lpSecurityDescriptor;
            public int bInheritHandle;
        }

        private const string LOW_INTEGRITY_SSL_SACL = "S:(ML;;NW;;;LW)";

        public static SafePipeHandle CreateLowIntegrityNamedPipe(string pipeName)
        {
            // convert the security descriptor
            IntPtr securityDescriptorPtr = IntPtr.Zero;
            int securityDescriptorSize = 0;
            bool result = ConvertStringSecurityDescriptorToSecurityDescriptor(
                LOW_INTEGRITY_SSL_SACL, 1, out securityDescriptorPtr, out securityDescriptorSize);
            if (!result)
                throw new Win32Exception(Marshal.GetLastWin32Error());

            SECURITY_ATTRIBUTES securityAttributes = new SECURITY_ATTRIBUTES();
            securityAttributes.nLength = Marshal.SizeOf(securityAttributes);
            securityAttributes.bInheritHandle = 1;
            securityAttributes.lpSecurityDescriptor = securityDescriptorPtr;

            SafePipeHandle handle = CreateNamedPipe(@"\\.\pipe\" + pipeName,
                PipeDirection.InOut, 100, PipeTransmissionMode.Byte, PipeOptions.Asynchronous,
                0, 0, PipeAccessRights.ReadWrite, securityAttributes);
            if (handle.IsInvalid)
                throw new Win32Exception(Marshal.GetLastWin32Error());

            return handle;
        }

        private static SafePipeHandle CreateNamedPipe(string fullPipeName, PipeDirection direction,
            int maxNumberOfServerInstances, PipeTransmissionMode transmissionMode, PipeOptions options,
            int inBufferSize, int outBufferSize, PipeAccessRights rights, SECURITY_ATTRIBUTES secAttrs)
        {
            int openMode = (int)direction | (int)options;
            int pipeMode = 0;
            if (maxNumberOfServerInstances == -1)
                maxNumberOfServerInstances = 0xff;

            SafePipeHandle handle = CreateNamedPipe(fullPipeName, openMode, pipeMode,
                maxNumberOfServerInstances, outBufferSize, inBufferSize, 0, secAttrs);
            if (handle.IsInvalid)
                throw new Win32Exception(Marshal.GetLastWin32Error());
            return handle;
        }

    }
}

Client code

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Pipes;

namespace NamedPipeClient
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                var pipeClient = new NamedPipeClientStream(".", "NamedPipe/Test",
                    PipeDirection.InOut,
                    PipeOptions.None);
                pipeClient.Connect(100);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Failed: " + ex);
                return;
            }

            Console.WriteLine("Connected");
            Console.ReadLine();
        }
    }
}
5
  • The problem may be in your LOW_INTEGRITY_SSL_SACL string of "S:(ML;;NW;;;LW)". The NW "denotes the blocking of a process at a lower integrity level from writing to an object at a higher integrity level (SDDL_NO_WRITE_UP)." Removing the NW might solve your problem.
    – HappyNomad
    Commented Jan 11, 2011 at 14:02
  • Did you solve problem? I tried several solutions, but on my Windows 7 SP1 Enterprise 64-bit with UAC fully disabled Low pipe by any means cannot access Medium pipe with lowerd security SIDs. Commented Jan 18, 2013 at 12:53
  • @DzmitryLahoda: no, I did not manage to solve it. I had to proceed differently and run the server in medium integrity level, instead. This is actually a safer approach as, in the end, there was no real need with running the server in admin mode. Commented Jan 21, 2013 at 10:24
  • I managed to fix similar problem after several days of hacking stackoverflow.com/questions/3282365/… Commented Jan 21, 2013 at 10:49
  • Just a quick note that Windows XP does not support integrity levels. Thus, for example the call to ConvertStringSecurityDescriptorToSecurityDescriptor() will fail with a windows code of 1804 (incorrect data type). -- not obvious. Hope this saves someone a minute or two should you run across this if running the code on Windows XP.
    – CodeWhore
    Commented Dec 1, 2014 at 20:56

3 Answers 3

7

Works for me on Windows 7 SP1

public static class NativeMethods
{
    public const string LOW_INTEGRITY_SSL_SACL = "S:(ML;;NW;;;LW)";

    public static int ERROR_SUCCESS = 0x0;

    public const int LABEL_SECURITY_INFORMATION = 0x00000010;

    public enum SE_OBJECT_TYPE
    {
        SE_UNKNOWN_OBJECT_TYPE = 0,
        SE_FILE_OBJECT,
        SE_SERVICE,
        SE_PRINTER,
        SE_REGISTRY_KEY,
        SE_LMSHARE,
        SE_KERNEL_OBJECT,
        SE_WINDOW_OBJECT,
        SE_DS_OBJECT,
        SE_DS_OBJECT_ALL,
        SE_PROVIDER_DEFINED_OBJECT,
        SE_WMIGUID_OBJECT,
        SE_REGISTRY_WOW64_32KEY
    }



    [DllImport("advapi32.dll", EntryPoint = "ConvertStringSecurityDescriptorToSecurityDescriptorW")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern Boolean ConvertStringSecurityDescriptorToSecurityDescriptor(
        [MarshalAs(UnmanagedType.LPWStr)] String strSecurityDescriptor,
        UInt32 sDRevision,
        ref IntPtr securityDescriptor,
        ref UInt32 securityDescriptorSize);

    [DllImport("kernel32.dll", EntryPoint = "LocalFree")]
    public static extern UInt32 LocalFree(IntPtr hMem);

    [DllImport("Advapi32.dll", EntryPoint = "SetSecurityInfo")]
    public static extern int SetSecurityInfo(SafeHandle hFileMappingObject,
                                                SE_OBJECT_TYPE objectType,
                                                Int32 securityInfo,
                                                IntPtr psidOwner,
                                                IntPtr psidGroup,
                                                IntPtr pDacl,
                                                IntPtr pSacl);
    [DllImport("advapi32.dll", EntryPoint = "GetSecurityDescriptorSacl")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern Boolean GetSecurityDescriptorSacl(
        IntPtr pSecurityDescriptor,
        out IntPtr lpbSaclPresent,
        out IntPtr pSacl,
        out IntPtr lpbSaclDefaulted);
}

public class InterProcessSecurity
{

    public static void SetLowIntegrityLevel(SafeHandle hObject)
    {
        IntPtr pSD = IntPtr.Zero;
        IntPtr pSacl;
        IntPtr lpbSaclPresent;
        IntPtr lpbSaclDefaulted;
        uint securityDescriptorSize = 0;

        if (NativeMethods.ConvertStringSecurityDescriptorToSecurityDescriptor(NativeMethods.LOW_INTEGRITY_SSL_SACL, 1, ref pSD, ref securityDescriptorSize))
        {
            if (NativeMethods.GetSecurityDescriptorSacl(pSD, out lpbSaclPresent, out pSacl, out lpbSaclDefaulted))
            {
                var err = NativeMethods.SetSecurityInfo(hObject,
                                              NativeMethods.SE_OBJECT_TYPE.SE_KERNEL_OBJECT,
                                              NativeMethods.LABEL_SECURITY_INFORMATION,
                                              IntPtr.Zero,
                                              IntPtr.Zero,
                                              IntPtr.Zero,
                                              pSacl);
                if (err != NativeMethods.ERROR_SUCCESS)
                {
                    throw new Win32Exception(err);
                }
            }
            NativeMethods.LocalFree(pSD);
        }
    }
}

Setup of server side

   InterProcessSecurity.SetLowIntegrityLevel(pipeServer.SafePipeHandle);
4

Your code for setting the mandatory integrity label on the pipe achieves this successfully. However, because your security descriptor doesn't define a DACL, your pipe is being created with the default one.

It is the DACL which is causing the low integrity client to fail when trying to connect to the pipe created by your high integrity server.

You need to fix the DACL in the server before opening the listener. Rather than trying to construct the full descriptor using P/Invoke code before creating the pipe, which is pretty hard to get right, I'd suggest leveraging the System.IO.Pipes classes to do it in managed code as a separate step after the pipe is created, like this:

    // Fix up the DACL on the pipe before opening the listener instance
    // This won't disturb the SACL containing the mandatory integrity label
    NamedPipeServerStream handleForSecurity = null;
    try
    {
        handleForSecurity = new NamedPipeServerStream("NamedPipe/Test", PipeDirection.InOut, -1, PipeTransmissionMode.Byte, PipeOptions.None, 0, 0, null, System.IO.HandleInheritability.None, PipeAccessRights.ChangePermissions);
        PipeSecurity ps = handleForSecurity.GetAccessControl();
        PipeAccessRule aceClients = new PipeAccessRule(
            new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null), // or some other group defining the allowed clients
            PipeAccessRights.ReadWrite, 
            AccessControlType.Allow);
        PipeAccessRule aceOwner = new PipeAccessRule(
            WindowsIdentity.GetCurrent().Owner,
            PipeAccessRights.FullControl,
            AccessControlType.Allow);
        ps.AddAccessRule(aceClients);
        ps.AddAccessRule(aceOwner);
        handleForSecurity.SetAccessControl(ps);
    }
    finally
    {
        if (null != handleForSecurity) handleForSecurity.Close();
        handleForSecurity = null;
    }

This works for me, with the rest of your code unchanged.

8
  • I couldn't get this to work. Could you link to a runnable example?
    – HappyNomad
    Commented Jan 11, 2011 at 13:55
  • @HappyNomad: Paste the code from my answer into the OP's Main method, between the first and second lines i.e. after he has got a handle from his pipe factory class, but before he instantiates the NamedPipeServerStream. Commented Jan 11, 2011 at 14:07
  • I'm still getting an AccessValidationException. Please have a look: sites.google.com/site/happynomad121/CustomPipeServer.cs
    – HappyNomad
    Commented Jan 11, 2011 at 23:21
  • Have you tried running the OP code with my patch? It works for me (on Vista Business SP2) - that is, gives the OP's expected results "Connected" in all integrity combinations. I have also extended his code to write a message from the client and receive it in the server. Everything works. Your code is different, and what you have provided doesn't compile, so I haven't spent any time on it. If you have an additional problem for your scenario, please ask a new SO question. Commented Jan 12, 2011 at 9:58
  • Chris, please provide a link to your solution, including the part which sends a message from the client to server.
    – HappyNomad
    Commented Jan 13, 2011 at 2:16
4

The answer I posted in December does work, despite the anonymous drive-by voting down which someone indulged themselves in. (At least, it does on Vista SP2 and I don't think there are any differences between Vista and Windows 7 which would affect this issue).

Here is a different approach which also works, specifying the DACL within the SDDL string used inside the pipe factory class:

Change the line in the CreateLowIntegrityNamedPipe(string pipeName) method which calls ConvertStringSecurityDescriptorToSecurityDescriptor, thus:

bool result = ConvertStringSecurityDescriptorToSecurityDescriptor(
     CreateSddlForPipeSecurity(), 1, out securityDescriptorPtr, 
     out securityDescriptorSize);

and provide an additional private static method, something like:

    private static string CreateSddlForPipeSecurity()
    {
        const string LOW_INTEGRITY_LABEL_SACL = "S:(ML;;NW;;;LW)";
        const string EVERYONE_CLIENT_ACE = "(A;;0x12019b;;;WD)";
        const string CALLER_ACE_TEMPLATE = "(A;;0x12019f;;;{0})";

        StringBuilder sb = new StringBuilder();
        sb.Append(LOW_INTEGRITY_LABEL_SACL);
        sb.Append("D:");
        sb.Append(EVERYONE_CLIENT_ACE);
        sb.AppendFormat(CALLER_ACE_TEMPLATE, WindowsIdentity.GetCurrent().Owner.Value);
        return sb.ToString();
    }

My version sets the pipe access to allow any authenticated user to be a pipe client. You could add additional features to the pipe factory class to specify a list of allowed client SIDs or such like.

2
  • Thanks for a very helpful answer with the EVERYONE_CLIENT_ACE suggestion Chris. This fixed the same problem for me. In my implementation I've simplified the "rights" bitmask to just "FA" (file all), so that it becomes D:(A;;FA;;;WD). This grants all file permissions to everyone, which is slightly more permissive than your bitmask. Commented Apr 16, 2023 at 11:25
  • Also, I didn't need to utilize the CALLER_ACE_TEMPLATE to establish a pipe connection to a low-IL process. I therefore suspect that part is redundant. Commented Apr 16, 2023 at 11:31

Not the answer you're looking for? Browse other questions tagged or ask your own question.