9

I'm trying to use PipeSecurity to secure a NamedPipeServerStream. When I call this.pipeServer.SetAccessControl(pipeSecurity) in the snippet below I get the following exception:

Attempted to perform an unauthorized operation.
    at System.Security.AccessControl.Win32.SetSecurityInfo(ResourceType type, String name, SafeHandle handle, SecurityInfos securityInformation, SecurityIdentifier owner, SecurityIdentifier group, GenericAcl sacl, GenericAcl dacl)
    at System.Security.AccessControl.NativeObjectSecurity.Persist(String name, SafeHandle handle, AccessControlSections includeSections, Object exceptionContext)
    at System.Security.AccessControl.NativeObjectSecurity.Persist(SafeHandle handle, AccessControlSections includeSections, Object exceptionContext)
    at System.IO.Pipes.PipeSecurity.Persist(SafeHandle handle)

code:

this.pipeServer =
    new NamedPipeServerStream(
        pipeName,
        PipeDirection.InOut,
        1,
        PipeTransmissionMode.Byte,
        PipeOptions.Asynchronous);

PipeSecurity pipeSecurity = new PipeSecurity();

WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(identity);

if (principal.IsInRole(WindowsBuiltInRole.Administrator))
{
    // Allow the Administrators group full access to the pipe.
    pipeSecurity.AddAccessRule(new PipeAccessRule(
        new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null).Translate(typeof(NTAccount)),
        PipeAccessRights.FullControl, AccessControlType.Allow));
} else {
    // Allow current user read and write access to the pipe.
    pipeSecurity.AddAccessRule(new PipeAccessRule(
        WindowsIdentity.GetCurrent().User,
        PipeAccessRights.ReadWrite, AccessControlType.Allow));
}

this.pipeServer.SetAccessControl(pipeSecurity);

Any ideas of what I'm doing wrong?

This is happening in .NET Framework (targeting net451) and .NET Standard 1.6 using the System.IO.AccessControl nuget package:

https://www.nuget.org/packages/System.IO.Pipes.AccessControl/

Edit:

I was able to use an #ifdef to use the following constructor that worked for .NET Framework:

public NamedPipeServerStream (string pipeName, System.IO.Pipes.PipeDirection direction, int maxNumberOfServerInstances, System.IO.Pipes.PipeTransmissionMode transmissionMode, System.IO.Pipes.PipeOptions options, int inBufferSize, int outBufferSize, System.IO.Pipes.PipeSecurity pipeSecurity)

However, this constructor does not exist in .NET Standard. I tried using this function that was added to .NET Core:

PipesAclExtensions.SetAccessControl(PipeStream, PipeSecurity)

But that yields the same exception from before.

I created a gist to show this.

  • Does https://social.msdn.microsoft.com/Forums/vstudio/en-US/5966ab37-afec-4b96-8106-4de0fbc70040/changing-permissions-on-a-named-pipe?forum=netfxbcl help? – mjwills Jun 06 '18 at 02:48
  • Possible duplicate of [Setting named pipe security in a Domain](https://stackoverflow.com/questions/1144093/setting-named-pipe-security-in-a-domain) – mjwills Jun 06 '18 at 02:49
  • 1
    This is not a duplicate - he is targeting .NET standard 1.6 - that version does not have the constructor that is suggested in the solution of the link you provided. – user3141326 Jun 06 '18 at 14:44
  • @mjwills thanks for those links, it fixed me for .NET Framework but as user3141326 states, that constructor does not exist in .NET Standard. Please see my edit. – Tyler James Leonhardt Jun 06 '18 at 15:11

5 Answers5

9

In NET 5 you can create a NamedPipeServerStream available to all user accounts by defining it like this:

PipeSecurity pipeSecurity = new PipeSecurity();

pipeSecurity.AddAccessRule(
    new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null),
    PipeAccessRights.ReadWrite, AccessControlType.Allow));

using (var pipe = NamedPipeServerStreamAcl.Create(
    "MyAppPipeName", 
    PipeDirection.InOut, 1, 
    PipeTransmissionMode.Message, 
    PipeOptions.None, 0, 0, 
    pipeSecurity))
    {
        pipe.WaitForConnection();
        // ...
    }

Please note that this is for Windows operating system only.

McGuireV10
  • 9,572
  • 5
  • 48
  • 64
Alexandru Dicu
  • 1,151
  • 1
  • 16
  • 24
  • 2
    Can confirm this method also works for .NET 6 – Leo Bottaro Dec 06 '21 at 21:39
  • this is the good stuff, confirmed for .NET 7 – IneedHelp May 01 '23 at 08:04
  • So what's the client-side trick? It works locally, but over the network I get `IOException: The user name or password is incorrect` from a client defined as `NamedPipeClientStream(Server, PipeName, PipeDirection.InOut)` or with the longer constructor specifying `TokenImpersonationLevel.Anonymous`... bombs when I call `client.Connect(ms)`... – McGuireV10 Aug 07 '23 at 17:36
8

I just had the same issue and tried to track it down.

TL;DR

The current status (Feb-2019) is sad but true: it just does not work with the classes that are given in today's NET Standard.

Ticket references

Also interesting in this context could be the *nix-related

Possible workaround

One still can use native API calls to get security set up as desired, but that's not for the faint of heart. One basically needs to implement these steps:


PS: At least we now can look it up in the code where it is hitting the wall. Just imagine having that problem 20 years ago ...

JensG
  • 13,148
  • 4
  • 45
  • 55
4

Recently I encountered the same problem when translating the project to .Net Core.

I added a nuget package to help with the transition: https://www.nuget.org/packages/NamedPipeServerStream.NetFrameworkVersion/

  • The package targets .Net Standard 2.0 and contains the original constructor from the .Net Framework (which supports PipeSecurity, HandleInheritability, and PipeAccessRights).
  • I restored it from decompiled code without making any changes.
  • Exceptions are fully supported, no code has been lost.
  • Has a strong name.
  • The source code is also available.
Install-Package NamedPipeServerStream.NetFrameworkVersion
using System.IO.Pipes;

var pipeSecurity = new PipeSecurity();
pipeSecurity.AddAccessRule(new PipeAccessRule(WindowsIdentity.GetCurrent().Owner, PipeAccessRights.ReadWrite, AccessControlType.Allow));

using var serverStream = NamedPipeServerStreamConstructors.New(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous | PipeOptions.WriteThrough, 0, 0, pipeSecurity);
Konstantin S.
  • 1,307
  • 14
  • 19
3

I've managed to make the possible workaround JensG mentioned. I used this sample to build it: https://code.msdn.microsoft.com/CSNamedPipeServer-4c760c2c/sourcecode?fileId=21684&pathId=1498714400

    public static class NativeNamedPipeServer
    {
        public static SafePipeHandle CreateNamedPipeServer(string pipeName, string sddl)
        {
            return NativeMethod.CreateNamedPipe(
                @"\\.\pipe\" + pipeName, // The unique pipe name.
                PipeOpenMode.PIPE_ACCESS_DUPLEX | PipeOpenMode.ASYNCHRONOUS,
                PipeMode.PIPE_TYPE_BYTE,
                1, // Max server instances
                1024 * 16, // Output buffer size
                1024 * 16, // Input buffer size
                NMPWAIT_USE_DEFAULT_WAIT, // Time-out interval
                CreateNativePipeSecurity(sddl) // Pipe security attributes
            );
        }

        /// <summary>
        /// The CreateNativePipeSecurity function creates and initializes a new 
        /// SECURITY_ATTRIBUTES object to allow Authenticated Users read and 
        /// write access to a pipe, and to allow the Administrators group full 
        /// access to the pipe.
        /// </summary>
        /// <returns>
        /// A SECURITY_ATTRIBUTES object that allows Authenticated Users read and 
        /// write access to a pipe, and allows the Administrators group full 
        /// access to the pipe.
        /// </returns>
        /// <see cref="http://msdn.microsoft.com/en-us/library/aa365600(VS.85).aspx"/>
        private static SECURITY_ATTRIBUTES CreateNativePipeSecurity(string sddl)
        {
            if (!NativeMethod.ConvertStringSecurityDescriptorToSecurityDescriptor(
                sddl, 1, out var pSecurityDescriptor, IntPtr.Zero))
            {
                throw new Win32Exception();
            }

            SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
            sa.nLength = Marshal.SizeOf(sa);
            sa.lpSecurityDescriptor = pSecurityDescriptor;
            sa.bInheritHandle = false;
            return sa;
        }


        #region Native API Signatures and Types

        /// <summary>
        /// Named Pipe Open Modes
        /// http://msdn.microsoft.com/en-us/library/aa365596.aspx
        /// </summary>
        [Flags]
        internal enum PipeOpenMode : uint
        {
            PIPE_ACCESS_INBOUND = 0x00000001, // Inbound pipe access.
            PIPE_ACCESS_OUTBOUND = 0x00000002, // Outbound pipe access.
            PIPE_ACCESS_DUPLEX = 0x00000003, // Duplex pipe access.

            // added from C# PipeOptions.cs
            WRITE_THROUGH = 0x80000000,
            ASYNCHRONOUS = 0x40000000,
            CURRENT_USER_ONLY = 0x20000000
        }

        /// <summary>
        /// Named Pipe Type, Read, and Wait Modes
        /// http://msdn.microsoft.com/en-us/library/aa365605.aspx
        /// </summary>
        [Flags]
        internal enum PipeMode : uint
        {
            // Type Mode
            PIPE_TYPE_BYTE = 0x00000000, // Byte pipe type.
            PIPE_TYPE_MESSAGE = 0x00000004, // Message pipe type.

            // Read Mode
            PIPE_READMODE_BYTE = 0x00000000, // Read mode of type Byte.
            PIPE_READMODE_MESSAGE = 0x00000002, // Read mode of type Message.

            // Wait Mode
            PIPE_WAIT = 0x00000000, // Pipe blocking mode.
            PIPE_NOWAIT = 0x00000001 // Pipe non-blocking mode.
        }

        /// <summary>
        /// Uses the default time-out specified in a call to the 
        /// CreateNamedPipe method.
        /// </summary>
        internal const uint NMPWAIT_USE_DEFAULT_WAIT = 0x00000000;


        /// <summary>
        /// The SECURITY_ATTRIBUTES structure contains the security descriptor for 
        /// an object and specifies whether the handle retrieved by specifying 
        /// this structure is inheritable. This structure provides security 
        /// settings for objects created by various functions, such as CreateFile, 
        /// CreateNamedPipe, CreateProcess, RegCreateKeyEx, or RegSaveKeyEx.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        internal class SECURITY_ATTRIBUTES
        {
            public int nLength;
            public SafeLocalMemHandle lpSecurityDescriptor;
            public bool bInheritHandle;
        }


        /// <summary>
        /// Represents a wrapper class for a local memory pointer. 
        /// </summary>
        [SuppressUnmanagedCodeSecurity,
         HostProtection(SecurityAction.LinkDemand, MayLeakOnAbort = true)]
        internal sealed class SafeLocalMemHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            public SafeLocalMemHandle() : base(true)
            {
            }

            public SafeLocalMemHandle(IntPtr preexistingHandle, bool ownsHandle)
                : base(ownsHandle)
            {
                base.SetHandle(preexistingHandle);
            }

            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success),
             DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            private static extern IntPtr LocalFree(IntPtr hMem);

            protected override bool ReleaseHandle()
            {
                return (LocalFree(base.handle) == IntPtr.Zero);
            }
        }


        /// <summary>
        /// The class exposes Windows APIs to be used in this code sample.
        /// </summary>
        [SuppressUnmanagedCodeSecurity]
        internal class NativeMethod
        {
            /// <summary>
            /// Creates an instance of a named pipe and returns a handle for 
            /// subsequent pipe operations.
            /// </summary>
            /// <param name="pipeName">Pipe name</param>
            /// <param name="openMode">Pipe open mode</param>
            /// <param name="pipeMode">Pipe-specific modes</param>
            /// <param name="maxInstances">Maximum number of instances</param>
            /// <param name="outBufferSize">Output buffer size</param>
            /// <param name="inBufferSize">Input buffer size</param>
            /// <param name="defaultTimeout">Time-out interval</param>
            /// <param name="securityAttributes">Security attributes</param>
            /// <returns>If the function succeeds, the return value is a handle 
            /// to the server end of a named pipe instance.</returns>
            [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern SafePipeHandle CreateNamedPipe(string pipeName,
                PipeOpenMode openMode, PipeMode pipeMode, int maxInstances,
                int outBufferSize, int inBufferSize, uint defaultTimeout,
                SECURITY_ATTRIBUTES securityAttributes);


            /// <summary>
            /// The ConvertStringSecurityDescriptorToSecurityDescriptor function 
            /// converts a string-format security descriptor into a valid, 
            /// functional security descriptor.
            /// </summary>
            /// <param name="sddlSecurityDescriptor">
            /// A string containing the string-format security descriptor (SDDL) 
            /// to convert.
            /// </param>
            /// <param name="sddlRevision">
            /// The revision level of the sddlSecurityDescriptor string. 
            /// Currently this value must be 1.
            /// </param>
            /// <param name="pSecurityDescriptor">
            /// A pointer to a variable that receives a pointer to the converted 
            /// security descriptor.
            /// </param>
            /// <param name="securityDescriptorSize">
            /// A pointer to a variable that receives the size, in bytes, of the 
            /// converted security descriptor. This parameter can be IntPtr.Zero.
            /// </param>
            /// <returns>
            /// If the function succeeds, the return value is true.
            /// </returns>
            [return: MarshalAs(UnmanagedType.Bool)]
            [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern bool ConvertStringSecurityDescriptorToSecurityDescriptor(
                string sddlSecurityDescriptor, int sddlRevision,
                out SafeLocalMemHandle pSecurityDescriptor,
                IntPtr securityDescriptorSize);
        }

        #endregion
    }

The create:

            var safePipeHandle = NativeNamedPipeServer.CreateNamedPipeServer(_pipeName, 
                pipeSecurity.GetSecurityDescriptorSddlForm(AccessControlSections.Access));
            var stream = new NamedPipeServerStream(PipeDirection.InOut, true, false, safePipeHandle);

The trickiest part was to make asynchronous work as the original source did not have PipeOpenMode.ASYNCHRONOUS flag. Figured that out by checking .NET Core 3.0 code. Strangely enough, they have all the pipe security code in there, but just not the constructor for it. So an alternative way might be actually reflection.

Natan
  • 2,816
  • 20
  • 37
  • 1
    This worked thanks. I just had to play around with the sddl to allow anonymous users. Got it working though, thanks. – spmoolman Nov 08 '19 at 11:22
  • Thanks a lot for your effort. This works very well and is preferred by me to the NuGet package approach. If the client wants to they could also very easily extend the method to allow specifying the parameters which are hardcoded such as buffer sizes and pipe open mode. Very good job, sir! – Ceco Apr 16 '20 at 13:12
  • 0 I am trying to run this code, but I have stumbled on this line: pipeSecurity.GetSecurityDescriptorSddlForm(). How is pipeSecurity defined? – The Dark Brainer Jul 06 '20 at 20:45
  • Check the code snippets in the question to see how to create the pipeSecurity object. – Natan Jul 07 '20 at 07:13
0

An API has since been added to allow specifying an ACL when creating the pipe.

Pass in your PipeSecurity object to NamedPipeServerStreamAcl.Create and it should work.

RandomEngy
  • 14,931
  • 5
  • 70
  • 113