Skip to content

Cannot set ReadMode after creating a NamedPipeClientStream instance #83072

Closed
@carlossanlop

Description

@carlossanlop

Background and motivation

Cannot set ReadMode after creating a NamedPipeClientStream instance. This is due to not exposing the NamedPipeClientStream constructor which accepts a PipeAccessRights.

API Proposal

namespace System.IO.Pipes;

// existing type
public sealed partial class NamedPipeClientStream : PipeStream
{
    // new constructor
    [SupportedOSPlatform("windows")]
    public NamedPipeClientStream(String serverName, String pipeName, PipeAccessRights desiredAccessRights,
           PipeOptions options, TokenImpersonationLevel impersonationLevel, HandleInheritability inheritability)
}

// existing type moved from System.IO.Pipes.AccessControl assembly.
[System.FlagsAttribute]
public enum PipeAccessRights
{
    ReadData = 1,
    WriteData = 2,
    CreateNewInstance = 4,
    ReadExtendedAttributes = 8,
    WriteExtendedAttributes = 16,
    ReadAttributes = 128,
    WriteAttributes = 256,
    Write = 274,
    Delete = 65536,
    ReadPermissions = 131072,
    Read = 131209,
    ReadWrite = 131483,
    ChangePermissions = 262144,
    TakeOwnership = 524288,
    Synchronize = 1048576,
    FullControl = 2032031,
    AccessSystemSecurity = 16777216,
}

API Usage

var pipeOut = new NamedPipeServerStream("SomeNamedPipe",
                                        PipeDirection.Out,
                                        1,
                                        PipeTransmissionMode.Message);

var pipeIn = new NamedPipeClientStream(".", 
                             "SomeNamedPipe", 
                             PipeAccessRights.ReadData | PipeAccessRights.WriteAttributes, 
                             PipeOptions.None, 
                             System.Security.Principal.TokenImpersonationLevel.None, 
                             System.IO.HandleInheritability.None);

pipeIn.Connect();
pipeIn.ReadMode = PipeTransmissionMode.Message; 

Alternative Designs

We could expose this with a Create factory method instead, as was done for NamedPipeServerStream

public static class AnonymousPipeServerStreamAcl
{
public static System.IO.Pipes.AnonymousPipeServerStream Create(System.IO.Pipes.PipeDirection direction, System.IO.HandleInheritability inheritability, int bufferSize, System.IO.Pipes.PipeSecurity? pipeSecurity) { throw null; }
}
public static class NamedPipeServerStreamAcl
{
public static System.IO.Pipes.NamedPipeServerStream Create(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, System.IO.HandleInheritability inheritability = System.IO.HandleInheritability.None, System.IO.Pipes.PipeAccessRights additionalAccessRights = default) { throw null; }
}

However that's not necessary and probably a misnomer since this new constructor isn't really about ACLs, but instead access rights on the handle. Contrast that to those existing factory methods which take PipeSecurity which is an ACLs concept. Splitting hairs, perhaps, since the API is related to access and is likely not supported on non-Windows, but I think we can do better here by exposing the exact missing API so we should.

Risks

Moving types around carries with it some risk. Luckily all the assemblies here are in the shared framework (since 6.0) and referenced as a set so it's less of a risk,

Original issue

Consider this code:

var pipeOut = new NamedPipeServerStream("SomeNamedPipe",
                                        PipeDirection.Out,
                                        1,
                                        PipeTransmissionMode.Message);

var pipeIn = new NamedPipeClientStream(".",
                                       "SomeNamedPipe",
                                       PipeDirection.In);

pipeIn.Connect();
pipeIn.ReadMode = PipeTransmissionMode.Message;  // <-- exception thrown here

In .NET Framework, there used to be a constructor on the client side that accepted PipeAccessRights, which would solve the issue, and would allow the user to change ReadMode later:

var pipeIn = 
   new NamedPipeClientStream(".", 
                             "SomeNamedPipe", 
                             PipeAccessRights.ReadData | PipeAccessRights.WriteAttributes, 
                             PipeOptions.None, 
                             System.Security.Principal.TokenImpersonationLevel.None, 
                             System.IO.HandleInheritability.None);

Unfortunately, we haven't ported that constructor to .NET Core+.

The underlying problem is that, according to the documentation of the Windows method CreateNamedPipe, we need to pass the FILE_WRITE_ATTRIBUTES access right when constructing the pipe handle, to be able to modify it after creation. This is explained in the remarks: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#remarks

The .NET Framework solution worked because it would set FILE_WRITE_ATTRIBUTES when using the constructor that takes a PipeAccessRights argument.

Unfortunately, the only workaround available in .NET Core+ is to:

  • Manually construct the SafePipeHandle, making sure to pass FILE_WRITE_ATTRIBUTES to the P/Invoke, which is not straightforward.
  • Create the client stream instance using the constructor that takes the pipe handle, while also making sure to pass true to the argument that indicates that the pipe should get connected.
  • Set the ReadMode.

I wrote a working example of this workaround for .NET 6.0 and published it here: https://github.com/carlossanlop/experiments/tree/NamedPipeClientStream_ReadMode

But we should consider solving it in runtime.

Note that the workaround provided above had to call an internal constructor from PipeSecurity using reflection. It's another argumemt supporting the idea that we should provide the solution ourselves.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-approvedAPI was approved in API review, it can be implementedarea-System.IOin-prThere is an active PR which will close this issue when it is merged

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions