diff --git a/pkg/Microsoft.Private.CoreFx.NETCoreApp/Microsoft.Private.CoreFx.NETCoreApp.pkgproj b/pkg/Microsoft.Private.CoreFx.NETCoreApp/Microsoft.Private.CoreFx.NETCoreApp.pkgproj index dea59cc037c5..13d2249e6108 100644 --- a/pkg/Microsoft.Private.CoreFx.NETCoreApp/Microsoft.Private.CoreFx.NETCoreApp.pkgproj +++ b/pkg/Microsoft.Private.CoreFx.NETCoreApp/Microsoft.Private.CoreFx.NETCoreApp.pkgproj @@ -39,6 +39,7 @@ Microsoft.VisualBasic; Microsoft.Win32.Registry; System.IO.FileSystem.AccessControl; + System.IO.Pipes.AccessControl; System.Private.DataContractSerialization; System.Private.Uri; System.Private.Xml; diff --git a/pkg/Microsoft.Private.PackageBaseline/packageIndex.json b/pkg/Microsoft.Private.PackageBaseline/packageIndex.json index 8549c8b6e33f..e3cc9e2adb02 100644 --- a/pkg/Microsoft.Private.PackageBaseline/packageIndex.json +++ b/pkg/Microsoft.Private.PackageBaseline/packageIndex.json @@ -2150,7 +2150,9 @@ "4.4.0" ], "BaselineVersion": "4.4.0", - "InboxOn": {}, + "InboxOn": { + "uap10.0.16300": "4.0.3.0" + }, "AssemblyVersionInPackageVersion": { "4.0.1.0": "4.3.0", "4.0.2.0": "4.4.0", diff --git a/src/System.IO.Pipes.AccessControl/dir.props b/src/System.IO.Pipes.AccessControl/dir.props index 884a835fb089..8162e77906fb 100644 --- a/src/System.IO.Pipes.AccessControl/dir.props +++ b/src/System.IO.Pipes.AccessControl/dir.props @@ -4,5 +4,8 @@ 4.0.3.0 MSFT + true + false + true \ No newline at end of file diff --git a/src/System.IO.Pipes.AccessControl/pkg/System.IO.Pipes.AccessControl.pkgproj b/src/System.IO.Pipes.AccessControl/pkg/System.IO.Pipes.AccessControl.pkgproj index 107007637e90..fa080f3657f3 100644 --- a/src/System.IO.Pipes.AccessControl/pkg/System.IO.Pipes.AccessControl.pkgproj +++ b/src/System.IO.Pipes.AccessControl/pkg/System.IO.Pipes.AccessControl.pkgproj @@ -3,7 +3,7 @@ - net461;netcoreapp2.0;$(AllXamarinFrameworks) + net461;netcoreapp2.0;$(UAPvNextTFM);$(AllXamarinFrameworks) @@ -11,6 +11,10 @@ netcore50 + + + runtimes/win/lib/$(UAPvNextTFM) + \ No newline at end of file diff --git a/src/System.IO.Pipes.AccessControl/src/Configurations.props b/src/System.IO.Pipes.AccessControl/src/Configurations.props index 353d21cf4a15..8d588d57a7eb 100644 --- a/src/System.IO.Pipes.AccessControl/src/Configurations.props +++ b/src/System.IO.Pipes.AccessControl/src/Configurations.props @@ -3,6 +3,8 @@ netfx-Windows_NT; + netcoreapp-Windows_NT; + uap-Windows_NT; netstandard-Windows_NT; netstandard; diff --git a/src/System.IO.Pipes.AccessControl/src/System.IO.Pipes.AccessControl.csproj b/src/System.IO.Pipes.AccessControl/src/System.IO.Pipes.AccessControl.csproj index 15b3bcf8c415..08999a5208c0 100644 --- a/src/System.IO.Pipes.AccessControl/src/System.IO.Pipes.AccessControl.csproj +++ b/src/System.IO.Pipes.AccessControl/src/System.IO.Pipes.AccessControl.csproj @@ -4,32 +4,38 @@ System.IO.Pipes.AccessControl {40059634-BB03-4A6F-8657-CCE2D376BC8B} - true + false + true SR.PlatformNotSupported_AccessControl - true + true - - - - - - - + + + + + + - + - + + + + + + + - \ No newline at end of file + diff --git a/src/System.IO.Pipes/ref/System.IO.Pipes.cs b/src/System.IO.Pipes/ref/System.IO.Pipes.cs index 6e234afd8a2a..2b0142f143e0 100644 --- a/src/System.IO.Pipes/ref/System.IO.Pipes.cs +++ b/src/System.IO.Pipes/ref/System.IO.Pipes.cs @@ -90,6 +90,7 @@ public enum PipeDirection public enum PipeOptions { Asynchronous = 1073741824, + CurrentUserOnly = 536870912, None = 0, WriteThrough = -2147483648, } diff --git a/src/System.IO.Pipes/src/Resources/Strings.resx b/src/System.IO.Pipes/src/Resources/Strings.resx index d9d9e05e7ace..e35951ae3d6c 100644 --- a/src/System.IO.Pipes/src/Resources/Strings.resx +++ b/src/System.IO.Pipes/src/Resources/Strings.resx @@ -120,12 +120,18 @@ Non negative number is required. + + Invalid PipeAccessRights value. + Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection. pipeName cannot be an empty string. + + This flag may not be set on a pipe. + serverName cannot be an empty string. Use \\\".\\\" for current machine. @@ -201,6 +207,9 @@ The file '{0}' already exists. + + Pipe is broken. + IO operation was aborted unexpectedly. @@ -273,4 +282,7 @@ The path '{0}' is too long, or a component of the specified path is too long. + + Could not connect to the pipe because it was not owned by the current user. + \ No newline at end of file diff --git a/src/System.IO.Pipes/src/System.IO.Pipes.csproj b/src/System.IO.Pipes/src/System.IO.Pipes.csproj index d09d590d24c9..890853949c7a 100644 --- a/src/System.IO.Pipes/src/System.IO.Pipes.csproj +++ b/src/System.IO.Pipes/src/System.IO.Pipes.csproj @@ -140,7 +140,12 @@ + + + + + @@ -177,6 +182,9 @@ Common\Interop\Unix\Interop.IOErrors.cs + + Common\Interop\Unix\Interop.ChMod.cs + Common\Interop\Unix\Interop.Close.cs @@ -204,7 +212,7 @@ Common\Interop\Unix\Interop.OpenFlags.cs - + Common\Interop\Unix\Interop.Permissions.cs @@ -255,8 +263,15 @@ + + + + + + + diff --git a/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeClientStream.Unix.cs b/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeClientStream.Unix.cs index bd609d2f0bf1..d15862dded38 100644 --- a/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeClientStream.Unix.cs +++ b/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeClientStream.Unix.cs @@ -3,7 +3,9 @@ // See the LICENSE file in the project root for more information. using Microsoft.Win32.SafeHandles; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; using System.Net.Sockets; using System.Security; using System.Threading; @@ -49,6 +51,17 @@ private bool TryConnect(int timeout, CancellationToken cancellationToken) } } + try + { + ValidateRemotePipeUser(clientHandle); + } + catch (Exception) + { + clientHandle.Dispose(); + socket.Dispose(); + throw; + } + InitializeHandle(clientHandle, isExposed: false, isAsync: (_pipeOptions & PipeOptions.Asynchronous) != 0); State = PipeState.Connected; return true; @@ -84,6 +97,23 @@ public override int OutBufferSize } } + private void ValidateRemotePipeUser(SafePipeHandle handle) + { + if (!IsCurrentUserOnly) + return; + + uint userId = Interop.Sys.GetEUid(); + if (Interop.Sys.GetPeerID(handle, out uint serverOwner) == -1) + { + throw CreateExceptionForLastError(); + } + + if (userId != serverOwner) + { + throw new UnauthorizedAccessException(SR.UnauthorizedAccess_NotOwnedByCurrentUser); + } + } + // ----------------------------- // ---- PAL layer ends here ---- // ----------------------------- diff --git a/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeClientStream.Windows.cs b/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeClientStream.Windows.cs index 286e510eeef6..472d9a9ea7b9 100644 --- a/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeClientStream.Windows.cs +++ b/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeClientStream.Windows.cs @@ -2,12 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.Win32.SafeHandles; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -using System.Security; using System.Security.Principal; using System.Threading; +using Microsoft.Win32.SafeHandles; namespace System.IO.Pipes { @@ -25,7 +24,10 @@ private bool TryConnect(int timeout, CancellationToken cancellationToken) { Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = PipeStream.GetSecAttrs(_inheritability); - int _pipeFlags = (int)_pipeOptions; + // PipeOptions.CurrentUserOnly is special since it doesn't match directly to a corresponding Win32 valid flag. + // Remove it, while keeping others untouched since historically this has been used as a way to pass flags to + // CreateNamedPipeClient that were not defined in the enumeration. + int _pipeFlags = (int)(_pipeOptions & ~PipeOptions.CurrentUserOnly); if (_impersonationLevel != TokenImpersonationLevel.None) { _pipeFlags |= Interop.Kernel32.SecurityOptions.SECURITY_SQOS_PRESENT; @@ -99,10 +101,10 @@ private bool TryConnect(int timeout, CancellationToken cancellationToken) } } - // Success! + // Success! InitializeHandle(handle, false, (_pipeOptions & PipeOptions.Asynchronous) != 0); State = PipeState.Connected; - + ValidateRemotePipeUser(); return true; } @@ -129,6 +131,24 @@ public int NumberOfServerInstances } } + private void ValidateRemotePipeUser() + { + if (!IsCurrentUserOnly) + return; + + PipeSecurity accessControl = this.GetAccessControl(); + IdentityReference remoteOwnerSid = accessControl.GetOwner(typeof(SecurityIdentifier)); + using (WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent()) + { + SecurityIdentifier currentUserSid = currentIdentity.Owner; + if (remoteOwnerSid != currentUserSid) + { + State = PipeState.Closed; + throw new UnauthorizedAccessException(SR.UnauthorizedAccess_NotOwnedByCurrentUser); + } + } + } + // ----------------------------- // ---- PAL layer ends here ---- // ----------------------------- diff --git a/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeClientStream.cs b/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeClientStream.cs index cb5bda09773f..430f4000ea4a 100644 --- a/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeClientStream.cs +++ b/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeClientStream.cs @@ -7,6 +7,7 @@ using System.Security.Principal; using System.Threading; using System.Threading.Tasks; +using System.Diagnostics; namespace System.IO.Pipes { @@ -72,7 +73,7 @@ public NamedPipeClientStream(String serverName, String pipeName, PipeDirection d { throw new ArgumentException(SR.Argument_EmptyServerName); } - if ((options & ~(PipeOptions.WriteThrough | PipeOptions.Asynchronous)) != 0) + if ((options & ~(PipeOptions.WriteThrough | PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly)) != 0) { throw new ArgumentOutOfRangeException(nameof(options), SR.ArgumentOutOfRange_OptionsInvalid); } @@ -84,8 +85,12 @@ public NamedPipeClientStream(String serverName, String pipeName, PipeDirection d { throw new ArgumentOutOfRangeException(nameof(inheritability), SR.ArgumentOutOfRange_HandleInheritabilityNoneOrInheritable); } + if ((options & PipeOptions.CurrentUserOnly) != 0) + { + IsCurrentUserOnly = true; + } - _normalizedPipePath = GetPipePath(serverName, pipeName); + _normalizedPipePath = GetPipePath(serverName, pipeName, IsCurrentUserOnly); _direction = direction; _inheritability = inheritability; _impersonationLevel = impersonationLevel; diff --git a/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Unix.cs b/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Unix.cs index e2e5b7a88ff7..d8430991c449 100644 --- a/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Unix.cs +++ b/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Unix.cs @@ -41,7 +41,7 @@ private void Create(string pipeName, PipeDirection direction, int maxNumberOfSer // We don't have a good way to enforce maxNumberOfServerInstances across processes; we only factor it in // for streams created in this process. Between processes, we behave similarly to maxNumberOfServerInstances == 1, // in that the second process to come along and create a stream will find the pipe already in existence and will fail. - _instance = SharedServer.Get(GetPipePath(".", pipeName), maxNumberOfServerInstances); + _instance = SharedServer.Get(GetPipePath(".", pipeName, IsCurrentUserOnly), maxNumberOfServerInstances); _direction = direction; _options = options; @@ -128,7 +128,7 @@ public string GetImpersonationUserName() return name; } - throw CreateExceptionForLastError(); + throw CreateExceptionForLastError(_instance?.PipeName); } public override int InBufferSize @@ -171,13 +171,13 @@ public void RunAsClient(PipeStreamImpersonationWorker impersonationWorker) uint peerID; if (Interop.Sys.GetPeerID(handle, out peerID) == -1) { - throw CreateExceptionForLastError(); + throw CreateExceptionForLastError(_instance?.PipeName); } // set the effective userid of the current (server) process to the clientid if (Interop.Sys.SetEUid(peerID) == -1) { - throw CreateExceptionForLastError(); + throw CreateExceptionForLastError(_instance?.PipeName); } try @@ -191,14 +191,6 @@ public void RunAsClient(PipeStreamImpersonationWorker impersonationWorker) } } - private Exception CreateExceptionForLastError() - { - Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); - return error.Error == Interop.Error.ENOTSUP ? - new PlatformNotSupportedException(SR.Format(SR.PlatformNotSupported_OperatingSystemError, nameof(Interop.Error.ENOTSUP))) : - Interop.GetExceptionForIoErrno(error, _instance?.PipeName); - } - /// Shared resources for NamedPipeServerStreams in the same process created for the same path. private sealed class SharedServer { diff --git a/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Windows.cs b/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Windows.cs index 5f8fe4215d93..0ab964ae91a9 100644 --- a/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Windows.cs +++ b/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Windows.cs @@ -2,16 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.Win32.SafeHandles; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; using System.Runtime.CompilerServices; -using System.Security; -using System.Security.Permissions; +using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Security.Principal; using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.Win32.SafeHandles; namespace System.IO.Pipes { @@ -39,6 +39,25 @@ private void Create(string pipeName, PipeDirection direction, int maxNumberOfSer throw new ArgumentOutOfRangeException(nameof(pipeName), SR.ArgumentOutOfRange_AnonymousReserved); } + PipeSecurity pipeSecurity = null; + + if (IsCurrentUserOnly) + { + using (WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent()) + { + SecurityIdentifier identifier = currentIdentity.Owner; + PipeAccessRule rule = new PipeAccessRule(identifier, PipeAccessRights.ReadWrite, AccessControlType.Allow); + pipeSecurity = new PipeSecurity(); + + pipeSecurity.AddAccessRule(rule); + pipeSecurity.SetOwner(identifier); + } + + // PipeOptions.CurrentUserOnly is special since it doesn't match directly to a corresponding Win32 valid flag. + // Remove it, while keeping others untouched since historically this has been used as a way to pass flags to CreateNamedPipe + // that were not defined in the enumeration. + options &= ~PipeOptions.CurrentUserOnly; + } int openMode = ((int)direction) | (maxNumberOfServerInstances == 1 ? Interop.Kernel32.FileOperations.FILE_FLAG_FIRST_PIPE_INSTANCE : 0) | @@ -53,16 +72,27 @@ private void Create(string pipeName, PipeDirection direction, int maxNumberOfSer maxNumberOfServerInstances = 255; } - Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = PipeStream.GetSecAttrs(inheritability); - SafePipeHandle handle = Interop.Kernel32.CreateNamedPipe(fullPipeName, openMode, pipeModes, - maxNumberOfServerInstances, outBufferSize, inBufferSize, 0, ref secAttrs); + var pinningHandle = new GCHandle(); + try + { + Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = PipeStream.GetSecAttrs(inheritability, pipeSecurity, ref pinningHandle); + SafePipeHandle handle = Interop.Kernel32.CreateNamedPipe(fullPipeName, openMode, pipeModes, + maxNumberOfServerInstances, outBufferSize, inBufferSize, 0, ref secAttrs); - if (handle.IsInvalid) + if (handle.IsInvalid) + { + throw Win32Marshal.GetExceptionForLastWin32Error(); + } + + InitializeHandle(handle, false, (options & PipeOptions.Asynchronous) != 0); + } + finally { - throw Win32Marshal.GetExceptionForLastWin32Error(); + if (pinningHandle.IsAllocated) + { + pinningHandle.Free(); + } } - - InitializeHandle(handle, false, (options & PipeOptions.Asynchronous) != 0); } // This will wait until the client calls Connect(). If we return from this method, we guarantee that diff --git a/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.cs b/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.cs index 28476c4d7770..32c867a94281 100644 --- a/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.cs +++ b/src/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.cs @@ -7,6 +7,7 @@ using System.Security; using System.Threading; using System.Threading.Tasks; +using System.Diagnostics; namespace System.IO.Pipes { @@ -88,7 +89,7 @@ private NamedPipeServerStream(String pipeName, PipeDirection direction, int maxN { throw new ArgumentException(SR.Argument_NeedNonemptyPipeName); } - if ((options & ~(PipeOptions.WriteThrough | PipeOptions.Asynchronous)) != 0) + if ((options & ~(PipeOptions.WriteThrough | PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly)) != 0) { throw new ArgumentOutOfRangeException(nameof(options), SR.ArgumentOutOfRange_OptionsInvalid); } @@ -112,8 +113,13 @@ private NamedPipeServerStream(String pipeName, PipeDirection direction, int maxN throw new ArgumentOutOfRangeException(nameof(inheritability), SR.ArgumentOutOfRange_HandleInheritabilityNoneOrInheritable); } + if ((options & PipeOptions.CurrentUserOnly) != 0) + { + IsCurrentUserOnly = true; + } + Create(pipeName, direction, maxNumberOfServerInstances, transmissionMode, - options, inBufferSize, outBufferSize, inheritability); + options, inBufferSize, outBufferSize, inheritability); } // Create a NamedPipeServerStream from an existing server pipe handle. diff --git a/src/System.IO.Pipes.AccessControl/src/System/IO/PipeAccessRights.cs b/src/System.IO.Pipes/src/System/IO/Pipes/PipeAccessRights.cs similarity index 100% rename from src/System.IO.Pipes.AccessControl/src/System/IO/PipeAccessRights.cs rename to src/System.IO.Pipes/src/System/IO/Pipes/PipeAccessRights.cs diff --git a/src/System.IO.Pipes.AccessControl/src/System/IO/PipeAccessRule.cs b/src/System.IO.Pipes/src/System/IO/Pipes/PipeAccessRule.cs similarity index 100% rename from src/System.IO.Pipes.AccessControl/src/System/IO/PipeAccessRule.cs rename to src/System.IO.Pipes/src/System/IO/Pipes/PipeAccessRule.cs diff --git a/src/System.IO.Pipes.AccessControl/src/System/IO/PipeAuditRule.cs b/src/System.IO.Pipes/src/System/IO/Pipes/PipeAuditRule.cs similarity index 100% rename from src/System.IO.Pipes.AccessControl/src/System/IO/PipeAuditRule.cs rename to src/System.IO.Pipes/src/System/IO/Pipes/PipeAuditRule.cs diff --git a/src/System.IO.Pipes/src/System/IO/Pipes/PipeOptions.cs b/src/System.IO.Pipes/src/System/IO/Pipes/PipeOptions.cs index 4a36fda29b0c..6dc1e802ba2a 100644 --- a/src/System.IO.Pipes/src/System/IO/Pipes/PipeOptions.cs +++ b/src/System.IO.Pipes/src/System/IO/Pipes/PipeOptions.cs @@ -10,5 +10,6 @@ public enum PipeOptions None = 0x0, WriteThrough = unchecked((int)0x80000000), Asynchronous = unchecked((int)0x40000000), // corresponds to FILE_FLAG_OVERLAPPED + CurrentUserOnly = unchecked((int)0x20000000) } } diff --git a/src/System.IO.Pipes.AccessControl/src/System/IO/PipeSecurity.cs b/src/System.IO.Pipes/src/System/IO/Pipes/PipeSecurity.cs similarity index 100% rename from src/System.IO.Pipes.AccessControl/src/System/IO/PipeSecurity.cs rename to src/System.IO.Pipes/src/System/IO/Pipes/PipeSecurity.cs diff --git a/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Unix.cs b/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Unix.cs index 1f4d2a9e263a..d22aea13c946 100644 --- a/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Unix.cs +++ b/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Unix.cs @@ -8,6 +8,7 @@ using System.Net.Sockets; using System.Runtime.InteropServices; using System.Security; +using System.IO; using System.Threading; using System.Threading.Tasks; @@ -28,7 +29,7 @@ public abstract partial class PipeStream : Stream /// Prefix to prepend to all pipe names. private static readonly string s_pipePrefix = Path.Combine(Path.GetTempPath(), "CoreFxPipe_"); - internal static string GetPipePath(string serverName, string pipeName) + internal static string GetPipePath(string serverName, string pipeName, bool isCurrentUserOnly) { if (serverName != "." && serverName != Interop.Sys.GetHostName()) { @@ -58,6 +59,18 @@ internal static string GetPipePath(string serverName, string pipeName) // naming scheme used as that breaks the ability for code running on an older // runtime to connect to code running on the newer runtime. That means we're stuck // with a tmp file for the lifetime of the server stream. + if (isCurrentUserOnly) + { + string directory = Path.Combine(Path.GetTempPath(), ".NET" + Interop.Sys.GetEUid()); + Directory.CreateDirectory(directory); + if (Interop.Sys.ChMod(directory, (int)Interop.Sys.Permissions.S_IRWXU) == -1) + { + throw CreateExceptionForLastError(); + } + + return Path.Combine(directory, s_pipePrefix + pipeName); + } + return s_pipePrefix + pipeName; } @@ -473,5 +486,13 @@ internal static void ConfigureSocket( break; } } + + internal static Exception CreateExceptionForLastError(string pipeName = null) + { + Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + return error.Error == Interop.Error.ENOTSUP ? + new PlatformNotSupportedException(SR.Format(SR.PlatformNotSupported_OperatingSystemError, nameof(Interop.Error.ENOTSUP))) : + Interop.GetExceptionForIoErrno(error, pipeName); + } } } diff --git a/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Windows.cs b/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Windows.cs index 7f5e654bc8ba..9eb63d78f551 100644 --- a/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Windows.cs +++ b/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Windows.cs @@ -5,7 +5,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -using System.Security; +using System.Security.Principal; using System.Threading; using System.Threading.Tasks; using Microsoft.Win32.SafeHandles; @@ -17,7 +17,8 @@ public abstract partial class PipeStream : Stream internal const bool CheckOperationsRequiresSetHandle = true; internal ThreadPoolBoundHandle _threadPoolBinding; - internal static string GetPipePath(string serverName, string pipeName) + // In Windows we don't use isCurrentUserOnly flag for the path because we set an ACL to the pipe. + internal static string GetPipePath(string serverName, string pipeName, bool isCurrentUserOnly) { string normalizedPipePath = Path.GetFullPath(@"\\" + serverName + @"\pipe\" + pipeName); if (String.Equals(normalizedPipePath, @"\\.\pipe\" + AnonymousPipeName, StringComparison.OrdinalIgnoreCase)) @@ -412,6 +413,31 @@ internal static unsafe Interop.Kernel32.SECURITY_ATTRIBUTES GetSecAttrs(HandleIn return secAttrs; } + internal static unsafe Interop.Kernel32.SECURITY_ATTRIBUTES GetSecAttrs(HandleInheritability inheritability, PipeSecurity pipeSecurity, ref GCHandle pinningHandle) + { + Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = default(Interop.Kernel32.SECURITY_ATTRIBUTES); + secAttrs.nLength = (uint)sizeof(Interop.Kernel32.SECURITY_ATTRIBUTES); + + if ((inheritability & HandleInheritability.Inheritable) != 0) + { + secAttrs.bInheritHandle = Interop.BOOL.TRUE; + } + + if (pipeSecurity != null) + { + byte[] securityDescriptor = pipeSecurity.GetSecurityDescriptorBinaryForm(); + pinningHandle = GCHandle.Alloc(securityDescriptor, GCHandleType.Pinned); + fixed (byte* pSecurityDescriptor = securityDescriptor) + { + secAttrs.lpSecurityDescriptor = (IntPtr)pSecurityDescriptor; + } + } + + return secAttrs; + } + + + /// /// Determine pipe read mode from Win32 /// diff --git a/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.cs b/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.cs index 74fe15ed63ec..3788b43abfb5 100644 --- a/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.cs +++ b/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.cs @@ -20,6 +20,7 @@ public abstract partial class PipeStream : Stream private bool _canRead; private bool _canWrite; private bool _isAsync; + private bool _isCurrentUserOnly; private bool _isMessageComplete; private bool _isFromExistingHandle; private bool _isHandleExposed; @@ -630,5 +631,17 @@ internal PipeState State _state = value; } } + + internal bool IsCurrentUserOnly + { + get + { + return _isCurrentUserOnly; + } + set + { + _isCurrentUserOnly = value; + } + } } } diff --git a/src/System.IO.Pipes.AccessControl/src/System/IO/PipesAclExtensions.cs b/src/System.IO.Pipes/src/System/IO/Pipes/PipesAclExtensions.cs similarity index 100% rename from src/System.IO.Pipes.AccessControl/src/System/IO/PipesAclExtensions.cs rename to src/System.IO.Pipes/src/System/IO/Pipes/PipesAclExtensions.cs diff --git a/src/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.netcoreapp.cs b/src/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.netcoreapp.cs new file mode 100644 index 000000000000..97cf82e2b0c7 --- /dev/null +++ b/src/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.netcoreapp.cs @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace System.IO.Pipes.Tests +{ + /// + /// Tests for the constructors for NamedPipeClientStream + /// + public class NamedPipeTest_CurrentUserOnly : NamedPipeTestBase + { + [Fact] + public static void CreateClient_CurrentUserOnly() + { + // Should not throw. + new NamedPipeClientStream(".", GetUniquePipeName(), PipeDirection.InOut, PipeOptions.CurrentUserOnly).Dispose(); + } + + [Fact] + public static void CreateServer_CurrentUserOnly() + { + // Should not throw. + new NamedPipeServerStream(GetUniquePipeName(), PipeDirection.InOut, 2, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly).Dispose(); + } + + [Fact] + public static void CreateServer_ConnectClient() + { + var name = GetUniquePipeName(); + using (var server = new NamedPipeServerStream(name, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly)) + { + using (var client = new NamedPipeClientStream(".", name, PipeDirection.InOut, PipeOptions.CurrentUserOnly)) + { + // Should not fail to connect since both, the server and client have the same owner. + client.Connect(); + } + } + } + + [Fact] + public static void CreateMultipleServers_ConnectMultipleClients() + { + var name1 = GetUniquePipeName(); + var name2 = GetUniquePipeName(); + var name3 = GetUniquePipeName(); + using (var server1 = new NamedPipeServerStream(name1, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly)) + using (var server2 = new NamedPipeServerStream(name2, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly)) + using (var server3 = new NamedPipeServerStream(name3, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly)) + { + using (var client1 = new NamedPipeClientStream(".", name1, PipeDirection.InOut, PipeOptions.CurrentUserOnly)) + using (var client2 = new NamedPipeClientStream(".", name2, PipeDirection.InOut, PipeOptions.CurrentUserOnly)) + using (var client3 = new NamedPipeClientStream(".", name3, PipeDirection.InOut, PipeOptions.CurrentUserOnly)) + { + client1.Connect(); + client2.Connect(); + client3.Connect(); + } + } + } + + [Fact] + public static void CreateMultipleServers_ConnectMultipleClients_MultipleThreads() + { + List tasks = new List(); + for (int i = 0; i < 3; i++) + { + tasks.Add(Task.Run(() => + { + var name = GetUniquePipeName(); + using (var server = new NamedPipeServerStream(name, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly)) + { + using (var client = new NamedPipeClientStream(".", name, PipeDirection.InOut, PipeOptions.CurrentUserOnly)) + { + // Should not fail to connect since both, the server and client have the same owner. + client.Connect(); + } + } + })); + } + + Task.WaitAll(tasks.ToArray()); + } + } +} \ No newline at end of file diff --git a/src/System.IO.Pipes/tests/System.IO.Pipes.Tests.csproj b/src/System.IO.Pipes/tests/System.IO.Pipes.Tests.csproj index d9cd837ac665..ebe1e38a1c5b 100644 --- a/src/System.IO.Pipes/tests/System.IO.Pipes.Tests.csproj +++ b/src/System.IO.Pipes/tests/System.IO.Pipes.Tests.csproj @@ -32,13 +32,16 @@ - - + + + + +