Skip to content

Commit

Permalink
Support setting NamedPipeClientStream.ReadMode with PipeAccessRights …
Browse files Browse the repository at this point in the history
…ctor overload (#100001)
  • Loading branch information
jeffhandley authored Mar 27, 2024
1 parent a57fa6f commit ddbb712
Show file tree
Hide file tree
Showing 15 changed files with 334 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.IO.Pipes.PipeAccessRights))]
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,6 @@ 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; }
}
[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,
}
public sealed partial class PipeAccessRule : System.Security.AccessControl.AccessRule
{
public PipeAccessRule(System.Security.Principal.IdentityReference identity, System.IO.Pipes.PipeAccessRights rights, System.Security.AccessControl.AccessControlType type) : base (default(System.Security.Principal.IdentityReference), default(int), default(bool), default(System.Security.AccessControl.InheritanceFlags), default(System.Security.AccessControl.PropagationFlags), default(System.Security.AccessControl.AccessControlType)) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

<ItemGroup>
<Compile Include="System.IO.Pipes.AccessControl.cs" />
<Compile Include="System.IO.Pipes.AccessControl.TypeForwards.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
23 changes: 23 additions & 0 deletions src/libraries/System.IO.Pipes/ref/System.IO.Pipes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public sealed partial class NamedPipeClientStream : System.IO.Pipes.PipeStream
public NamedPipeClientStream(System.IO.Pipes.PipeDirection direction, bool isAsync, bool isConnected, Microsoft.Win32.SafeHandles.SafePipeHandle safePipeHandle) : base (default(System.IO.Pipes.PipeDirection), default(int)) { }
public NamedPipeClientStream(string pipeName) : base (default(System.IO.Pipes.PipeDirection), default(int)) { }
public NamedPipeClientStream(string serverName, string pipeName) : base (default(System.IO.Pipes.PipeDirection), default(int)) { }
[System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")]
public NamedPipeClientStream(string serverName, string pipeName, System.IO.Pipes.PipeAccessRights desiredAccessRights, PipeOptions options, System.Security.Principal.TokenImpersonationLevel impersonationLevel, HandleInheritability inheritability) : base(default(System.IO.Pipes.PipeDirection), default(int)) { }
public NamedPipeClientStream(string serverName, string pipeName, System.IO.Pipes.PipeDirection direction) : base (default(System.IO.Pipes.PipeDirection), default(int)) { }
public NamedPipeClientStream(string serverName, string pipeName, System.IO.Pipes.PipeDirection direction, System.IO.Pipes.PipeOptions options) : base (default(System.IO.Pipes.PipeDirection), default(int)) { }
public NamedPipeClientStream(string serverName, string pipeName, System.IO.Pipes.PipeDirection direction, System.IO.Pipes.PipeOptions options, System.Security.Principal.TokenImpersonationLevel impersonationLevel) : base (default(System.IO.Pipes.PipeDirection), default(int)) { }
Expand Down Expand Up @@ -82,6 +84,27 @@ public void WaitForConnection() { }
public System.Threading.Tasks.Task WaitForConnectionAsync() { throw null; }
public System.Threading.Tasks.Task WaitForConnectionAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
}
[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,
}
public enum PipeDirection
{
In = 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<!-- Exposed public in System.IO.Pipes.AccessControl but implemented in System.IO.Pipes. -->
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:System.IO.Pipes.AnonymousPipeServerStreamAcl</Target>
Expand All @@ -14,12 +13,6 @@
<Left>ref/net9.0/System.IO.Pipes.dll</Left>
<Right>runtimes/win/lib/net9.0/System.IO.Pipes.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:System.IO.Pipes.PipeAccessRights</Target>
<Left>ref/net9.0/System.IO.Pipes.dll</Left>
<Right>runtimes/win/lib/net9.0/System.IO.Pipes.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:System.IO.Pipes.PipeAccessRule</Target>
Expand Down
3 changes: 3 additions & 0 deletions src/libraries/System.IO.Pipes/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@
<data name="ArgumentOutOfRange_NeedValidPipeAccessRights" xml:space="preserve">
<value>Invalid PipeAccessRights value.</value>
</data>
<data name="PlatformNotSupported_PipeAccessRights" xml:space="preserve">
<value>Specifying PipeAccessRights is not supported on this platform.</value>
</data>
<data name="Argument_NonContainerInvalidAnyFlag" xml:space="preserve">
<value>This flag may not be set on a pipe.</value>
</data>
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/System.IO.Pipes/src/System.IO.Pipes.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<Compile Include="System\IO\Pipes\AnonymousPipeServerStream.cs" />
<Compile Include="System\IO\Pipes\NamedPipeClientStream.cs" />
<Compile Include="System\IO\Pipes\NamedPipeServerStream.cs" />
<Compile Include="System\IO\Pipes\PipeAccessRights.cs" />
<Compile Include="System\IO\Pipes\PipeDirection.cs" />
<Compile Include="System\IO\Pipes\PipeOptions.cs" />
<Compile Include="System\IO\Pipes\PipeState.cs" />
Expand Down Expand Up @@ -116,7 +117,6 @@
<Compile Include="System\IO\Pipes\NamedPipeClientStream.Windows.cs" />
<Compile Include="System\IO\Pipes\NamedPipeServerStream.Windows.cs" />
<Compile Include="System\IO\Pipes\NamedPipeServerStream.Win32.cs" />
<Compile Include="System\IO\Pipes\PipeAccessRights.cs" />
<Compile Include="System\IO\Pipes\PipeAccessRule.cs" />
<Compile Include="System\IO\Pipes\PipeAuditRule.cs" />
<Compile Include="System\IO\Pipes\PipesAclExtensions.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Principal;
using System.Threading;
using Microsoft.Win32.SafeHandles;

Expand All @@ -18,6 +19,16 @@ namespace System.IO.Pipes
/// </summary>
public sealed partial class NamedPipeClientStream : PipeStream
{
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
public NamedPipeClientStream(string serverName, string pipeName, PipeAccessRights desiredAccessRights,
PipeOptions options, TokenImpersonationLevel impersonationLevel, HandleInheritability inheritability)
: base(PipeDirection.InOut, 0)
{
throw new PlatformNotSupportedException(SR.PlatformNotSupported_PipeAccessRights);
}

private static int AccessRightsFromDirection(PipeDirection _) => 0;

private bool TryConnect(int _ /* timeout */)
{
// timeout isn't used as Connect will be very fast,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Principal;
Expand All @@ -16,6 +17,81 @@ namespace System.IO.Pipes
/// </summary>
public sealed partial class NamedPipeClientStream : PipeStream
{
/// <summary>
/// Initializes a new instance of the <see cref="NamedPipeClientStream"/> class with the specified pipe and server names,
/// the desired <see cref="PipeAccessRights"/>, and the specified impersonation level and inheritability.
/// </summary>
/// <param name="serverName">The name of the remote computer to connect to, or "." to specify the local computer.</param>
/// <param name="pipeName">The name of the pipe.</param>
/// <param name="desiredAccessRights">One of the enumeration values that specifies the desired access rights of the pipe.</param>
/// <param name="options">One of the enumeration values that determines how to open or create the pipe.</param>
/// <param name="impersonationLevel">One of the enumeration values that determines the security impersonation level.</param>
/// <param name="inheritability">One of the enumeration values that determines whether the underlying handle will be inheritable by child processes.</param>
/// <exception cref="ArgumentNullException"><paramref name="pipeName"/> or <paramref name="serverName"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException"><paramref name="pipeName"/> or <paramref name="serverName"/> is a zero-length string.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="pipeName"/> is set to "anonymous".</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="desiredAccessRights"/> is not a valid <see cref="PipeAccessRights"/> value.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="options"/> is not a valid <see cref="PipeOptions"/> value.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="impersonationLevel"/> is not a valid <see cref="TokenImpersonationLevel"/> value.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="inheritability"/> is not a valid <see cref="HandleInheritability"/> value.</exception>
/// <remarks>
/// The pipe direction for this constructor is determined by the <paramref name="desiredAccessRights"/> parameter.
/// If the <paramref name="desiredAccessRights"/> parameter specifies <see cref="PipeAccessRights.ReadData"/>,
/// the pipe direction is <see cref="PipeDirection.In"/>. If the <paramref name="desiredAccessRights"/> parameter
/// specifies <see cref="PipeAccessRights.WriteData"/>, the pipe direction is <see cref="PipeDirection.Out"/>.
/// If the value of <paramref name="desiredAccessRights"/> specifies both <see cref="PipeAccessRights.ReadData"/>
/// and <see cref="PipeAccessRights.WriteData"/>, the pipe direction is <see cref="PipeDirection.InOut"/>.
/// </remarks>
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
public NamedPipeClientStream(string serverName, string pipeName, PipeAccessRights desiredAccessRights,
PipeOptions options, TokenImpersonationLevel impersonationLevel, HandleInheritability inheritability)
: this(serverName, pipeName, DirectionFromRights(desiredAccessRights), options, impersonationLevel, inheritability)
{
_accessRights = (int)desiredAccessRights;
}

private static PipeDirection DirectionFromRights(PipeAccessRights desiredAccessRights, [CallerArgumentExpression(nameof(desiredAccessRights))] string? argumentName = null)
{
// Validate the desiredAccessRights parameter here to ensure an invalid value does not result
// in an argument exception being thrown for the direction argument
// Throw if there are any unrecognized bits
// Throw if neither ReadData nor WriteData are specified, as this will result in an invalid PipeDirection
if ((desiredAccessRights & ~(PipeAccessRights.FullControl | PipeAccessRights.AccessSystemSecurity)) != 0 ||
((desiredAccessRights & (PipeAccessRights.ReadData | PipeAccessRights.WriteData)) == 0))
{
throw new ArgumentOutOfRangeException(argumentName, SR.ArgumentOutOfRange_NeedValidPipeAccessRights);
}

PipeDirection direction = 0;

if ((desiredAccessRights & PipeAccessRights.ReadData) != 0)
{
direction |= PipeDirection.In;
}
if ((desiredAccessRights & PipeAccessRights.WriteData) != 0)
{
direction |= PipeDirection.Out;
}

return direction;
}

private static int AccessRightsFromDirection(PipeDirection direction)
{
int access = 0;

if ((PipeDirection.In & direction) != 0)
{
access |= Interop.Kernel32.GenericOperations.GENERIC_READ;
}
if ((PipeDirection.Out & direction) != 0)
{
access |= Interop.Kernel32.GenericOperations.GENERIC_WRITE;
}

return access;
}

// Waits for a pipe instance to become available. This method may return before WaitForConnection is called
// on the server end, but WaitForConnection will not return until we have returned. Any data written to the
// pipe by us after we have connected but before the server has called WaitForConnection will be available
Expand All @@ -34,17 +110,7 @@ private bool TryConnect(int timeout)
_pipeFlags |= (((int)_impersonationLevel - 1) << 16);
}

int access = 0;
if ((PipeDirection.In & _direction) != 0)
{
access |= Interop.Kernel32.GenericOperations.GENERIC_READ;
}
if ((PipeDirection.Out & _direction) != 0)
{
access |= Interop.Kernel32.GenericOperations.GENERIC_WRITE;
}

SafePipeHandle handle = CreateNamedPipeClient(_normalizedPipePath, ref secAttrs, _pipeFlags, access);
SafePipeHandle handle = CreateNamedPipeClient(_normalizedPipePath, ref secAttrs, _pipeFlags, _accessRights);

if (handle.IsInvalid)
{
Expand Down Expand Up @@ -81,7 +147,7 @@ private bool TryConnect(int timeout)
}

// Pipe server should be free. Let's try to connect to it.
handle = CreateNamedPipeClient(_normalizedPipePath, ref secAttrs, _pipeFlags, access);
handle = CreateNamedPipeClient(_normalizedPipePath, ref secAttrs, _pipeFlags, _accessRights);

if (handle.IsInvalid)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public sealed partial class NamedPipeClientStream : PipeStream
private readonly PipeOptions _pipeOptions;
private readonly HandleInheritability _inheritability;
private readonly PipeDirection _direction;
private readonly int _accessRights;

// Creates a named pipe client using default server (same machine, or "."), and PipeDirection.InOut
public NamedPipeClientStream(string pipeName)
Expand Down Expand Up @@ -84,6 +85,7 @@ public NamedPipeClientStream(string serverName, string pipeName, PipeDirection d
_inheritability = inheritability;
_impersonationLevel = impersonationLevel;
_pipeOptions = options;
_accessRights = AccessRightsFromDirection(direction);
}

// Create a NamedPipeClientStream from an existing server pipe handle.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Security.Principal;
using Microsoft.Win32.SafeHandles;
using Xunit;

namespace System.IO.Pipes.Tests
{
/// <summary>
/// Unix-specific tests for the constructors for NamedPipeClientStream
/// </summary>
public partial class NamedPipeTest_CreateClient
{
[Fact]
public static void NotSupportedPipeAccessRights_Throws_PlatformNotSupportedException()
{
Assert.Throws<PlatformNotSupportedException>(() => new NamedPipeClientStream(".", "client1", PipeAccessRights.FullControl, PipeOptions.None, TokenImpersonationLevel.None, HandleInheritability.None));
}

[Fact]
public static void NotSupportedPipePath_Throws_PlatformNotSupportedException()
{
string hostName;
Assert.True(InteropTest.TryGetHostName(out hostName));

Assert.Throws<PlatformNotSupportedException>(() => new NamedPipeClientStream("foobar" + hostName, "foobar"));
Assert.Throws<PlatformNotSupportedException>(() => new NamedPipeClientStream(hostName, "foobar" + Path.GetInvalidFileNameChars()[0]));
Assert.Throws<PlatformNotSupportedException>(() => new NamedPipeClientStream(hostName, "/tmp/foo\0bar"));
Assert.Throws<PlatformNotSupportedException>(() => new NamedPipeClientStream(hostName, "/tmp/foobar/"));
Assert.Throws<PlatformNotSupportedException>(() => new NamedPipeClientStream(hostName, "/"));
Assert.Throws<PlatformNotSupportedException>(() => new NamedPipeClientStream(hostName, "\0"));
}
}
}
Loading

0 comments on commit ddbb712

Please sign in to comment.