Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support setting NamedPipeClientStream.ReadMode with PipeAccessRights ctor overload #100001

Merged
merged 7 commits into from
Mar 27, 2024
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 sealed partial class NamedPipeServerStream : System.IO.Pipes.PipeStream
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,
jeffhandley marked this conversation as resolved.
Show resolved Hide resolved
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"));
}
}
}