Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion pkg/Microsoft.Private.PackageBaseline/packageIndex.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions src/System.IO.Pipes.AccessControl/dir.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@
<PropertyGroup>
<AssemblyVersion>4.0.3.0</AssemblyVersion>
<AssemblyKey>MSFT</AssemblyKey>
<IsNETCoreApp>true</IsNETCoreApp>
<IsNETCoreAppRef>false</IsNETCoreAppRef>
<IsUAP>true</IsUAP>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
<ItemGroup>
<ProjectReference Include="..\ref\System.IO.Pipes.AccessControl.csproj">
<SupportedFramework>net461;netcoreapp2.0;$(AllXamarinFrameworks)</SupportedFramework>
<SupportedFramework>net461;netcoreapp2.0;$(UAPvNextTFM);$(AllXamarinFrameworks)</SupportedFramework>
</ProjectReference>
<ProjectReference Include="..\src\System.IO.Pipes.AccessControl.csproj" />
<HarvestIncludePaths Include="ref/net46;lib/net46;runtimes/win/lib/net46" />
<HarvestIncludePaths Include="ref/netstandard1.3">
<SupportedFramework>netcore50</SupportedFramework>
</HarvestIncludePaths>
<HarvestIncludePaths Include="runtimes/win/lib/netstandard1.3;lib/netstandard1.3" />
<InboxOnTargetFramework Include="$(UAPvNextTFM)" />
<File Include="$(PlaceHolderFile)">
<TargetPath>runtimes/win/lib/$(UAPvNextTFM)</TargetPath>
</File>
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project>
2 changes: 2 additions & 0 deletions src/System.IO.Pipes.AccessControl/src/Configurations.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<PropertyGroup>
<BuildConfigurations>
netfx-Windows_NT;
netcoreapp-Windows_NT;
uap-Windows_NT;
netstandard-Windows_NT;
netstandard;
</BuildConfigurations>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,38 @@
<PropertyGroup>
<AssemblyName>System.IO.Pipes.AccessControl</AssemblyName>
<ProjectGuid>{40059634-BB03-4A6F-8657-CCE2D376BC8B}</ProjectGuid>
<AllowUnsafeBlocks Condition="'$(TargetGroup)'=='netstandard'">true</AllowUnsafeBlocks>
<IncludeDefaultReferences Condition="'$(TargetGroup)' == 'netcoreapp' OR '$(TargetGroup)' == 'uap'">false</IncludeDefaultReferences>
<AllowUnsafeBlocks Condition="'$(TargetGroup)' == 'netstandard'">true</AllowUnsafeBlocks>
<GeneratePlatformNotSupportedAssemblyMessage Condition="'$(TargetGroup)' == 'netstandard' AND '$(TargetsWindows)' != 'true'">SR.PlatformNotSupported_AccessControl</GeneratePlatformNotSupportedAssemblyMessage>
<IsPartialFacadeAssembly Condition="'$(TargetGroup)'=='netfx'">true</IsPartialFacadeAssembly>
<IsPartialFacadeAssembly Condition="'$(TargetGroup)' != 'netstandard'">true</IsPartialFacadeAssembly>
</PropertyGroup>
<!-- Default configurations to help VS understand the options -->
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netfx-Windows_NT-Debug|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netfx-Windows_NT-Release|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Debug|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Release|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Windows_NT-Debug|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Windows_NT-Release|AnyCPU'" />
<ItemGroup Condition="'$(IsPartialFacadeAssembly)'!='true' AND '$(TargetsWindows)'=='true'">
<Compile Include="System\IO\PipeSecurity.cs" />
<Compile Include="System\IO\PipeAccessRights.cs" />
<Compile Include="System\IO\PipeAccessRule.cs" />
<Compile Include="System\IO\PipeAuditRule.cs" />
<Compile Include="System\IO\PipesAclExtensions.cs" />
<ItemGroup Condition="'$(TargetGroup)' == 'netstandard' AND '$(TargetsWindows)' == 'true'">
<Compile Include="..\..\System.IO.Pipes\src\System\IO\Pipes\PipeSecurity.cs" />
<Compile Include="..\..\System.IO.Pipes\src\System\IO\Pipes\PipeAccessRights.cs" />
<Compile Include="..\..\System.IO.Pipes\src\System\IO\Pipes\PipeAccessRule.cs" />
<Compile Include="..\..\System.IO.Pipes\src\System\IO\Pipes\PipeAuditRule.cs" />
<Compile Include="..\..\System.IO.Pipes\src\System\IO\Pipes\PipesAclExtensions.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetGroup)'=='netfx'">
<ItemGroup Condition="'$(TargetGroup)' == 'netfx'">
<Reference Include="mscorlib" />
<Reference Include="System.Core" />
<Compile Include="System\IO\PipesAclExtensions.net46.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetGroup)'!='netfx'">
<ItemGroup Condition="'$(TargetGroup)' == 'netstandard'">
<Reference Include="System.Security.AccessControl" />
<Reference Include="System.Security.Principal.Windows" />
</ItemGroup>
<ItemGroup Condition="'$(TargetGroup)' == 'netcoreapp' OR '$(TargetGroup)' == 'uap'">
<Reference Include="System.Runtime" />
<Reference Include="System.Resources.ResourceManager" />
<ProjectReference Include="..\..\System.IO.Pipes\src\System.IO.Pipes.csproj" />
<ProjectReference Include="..\..\System.Security.AccessControl\src\System.Security.AccessControl.csproj" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project>
</Project>
1 change: 1 addition & 0 deletions src/System.IO.Pipes/ref/System.IO.Pipes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public enum PipeDirection
public enum PipeOptions
{
Asynchronous = 1073741824,
CurrentUserOnly = 536870912,
None = 0,
WriteThrough = -2147483648,
}
Expand Down
12 changes: 12 additions & 0 deletions src/System.IO.Pipes/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,18 @@
<data name="ArgumentOutOfRange_NeedNonNegNum" xml:space="preserve">
<value>Non negative number is required.</value>
</data>
<data name="ArgumentOutOfRange_NeedValidPipeAccessRights" xml:space="preserve">
<value>Invalid PipeAccessRights value.</value>
</data>
<data name="Argument_InvalidOffLen" xml:space="preserve">
<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.</value>
</data>
<data name="Argument_NeedNonemptyPipeName" xml:space="preserve">
<value>pipeName cannot be an empty string.</value>
</data>
<data name="Argument_NonContainerInvalidAnyFlag" xml:space="preserve">
<value>This flag may not be set on a pipe.</value>
</data>
<data name="Argument_EmptyServerName" xml:space="preserve">
<value>serverName cannot be an empty string. Use \\\".\\\" for current machine.</value>
</data>
Expand Down Expand Up @@ -201,6 +207,9 @@
<data name="IO_FileExists_Name" xml:space="preserve">
<value>The file '{0}' already exists.</value>
</data>
<data name="IO_IO_PipeBroken" xml:space="preserve">
<value>Pipe is broken.</value>
</data>
<data name="IO_OperationAborted" xml:space="preserve">
<value>IO operation was aborted unexpectedly.</value>
</data>
Expand Down Expand Up @@ -273,4 +282,7 @@
<data name="IO_PathTooLong_Path" xml:space="preserve">
<value>The path '{0}' is too long, or a component of the specified path is too long.</value>
</data>
<data name="UnauthorizedAccess_NotOwnedByCurrentUser" xml:space="preserve">
<value>Could not connect to the pipe because it was not owned by the current user.</value>
</data>
</root>
17 changes: 16 additions & 1 deletion src/System.IO.Pipes/src/System.IO.Pipes.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,12 @@
<Compile Include="System\IO\Pipes\ConnectionCompletionSource.cs" />
<Compile Include="System\IO\Pipes\NamedPipeClientStream.Windows.cs" />
<Compile Include="System\IO\Pipes\NamedPipeServerStream.Windows.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" />
<Compile Include="System\IO\Pipes\PipeCompletionSource.cs" />
<Compile Include="System\IO\Pipes\PipeSecurity.cs" />
<Compile Include="System\IO\Pipes\PipeStream.Windows.cs" />
<Compile Include="System\IO\Pipes\ReadWriteCompletionSource.cs" />
</ItemGroup>
Expand Down Expand Up @@ -177,6 +182,9 @@
<Compile Include="$(CommonPath)\Interop\Unix\Interop.IOErrors.cs">
<Link>Common\Interop\Unix\Interop.IOErrors.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.ChMod.cs">
<Link>Common\Interop\Unix\Interop.ChMod.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.Close.cs">
<Link>Common\Interop\Unix\Interop.Close.cs</Link>
</Compile>
Expand Down Expand Up @@ -204,7 +212,7 @@
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.OpenFlags.cs">
<Link>Common\Interop\Unix\Interop.OpenFlags.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.Permissions.cs">
<Compile Include="$(CommonPath)\CoreLib\Interop\Unix\System.Native\Interop.Permissions.cs">
<Link>Common\Interop\Unix\Interop.Permissions.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.Pipe.cs">
Expand Down Expand Up @@ -255,8 +263,15 @@
<Reference Include="System.Threading.Overlapped" />
<Reference Include="System.Threading.Tasks" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
<Reference Include="System.Collections.NonGeneric" />
<Reference Include="System.Security.AccessControl" />
<Reference Include="System.Security.Principal.Windows" />
<Reference Include="System.Security.Claims" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsUnix)' == 'true'">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are we going to do on unix?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still reading about Unix security. Not yet what dependencies we're going to need. If any of the ones imported for windows is needed I will move it to the common ItemGroup.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Unix we will probably want to create a directory that's chmod'd to only be accessible by the current user, and then create the domain socket in that directory.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is the main thing still missing: right now there is enforcement on the client but nothing on the server side.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm working on it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you're saying. What if for the UserOnly pipes instead of adding the corefx prefix in the pipe name we use that space that we're already taking, for the directory name?

private static readonly string s_pipePrefix = Path.Combine(Path.GetTempPath(), "CoreFxPipe_");

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could create a directory that contains the userid so that it is unique and related to this user and we don't generate random names, so maybe something like: tempdir/{euid}_fx/{pipename} where an euid is using the 16 bits. As the docs say:

The original Linux getuid() and geteuid() system calls supported only 16-bit user IDs. Subsequently, Linux 2.4 added getuid32() and geteuid32(), supporting 32-bit IDs. The glibc getuid() and geteuid() wrapper functions transparently deal with the variations across kernel versions.

Which at the end will be using less space than what CoreFxPipe_ prefix is already using on a regular named pipe.

@stephentoub thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephentoub noting for posterity that on Roslyn they currently don't use this approach, they connect blindly then check GetPeerID() against GetEUid().

https://github.com/dotnet/roslyn/blob/c7a465fabaf688affbd5897d7348431eeb2059b5/src/Compilers/Shared/BuildServerConnection.cs#L490

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephentoub any reason to prefer one over the other? It seems like getpeerid() is for this exact purpose. https://linux.die.net/man/3/getpeereid

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danmosemsft I am starting to look back at this to add the tests this week, and I think your point about getpeerid is valid. The only thing that cross my mind is that the existing API on the server side currently has to either throw an exception for each mismatched peer (after the socket connection) or simply ignore them - neither seems good (perhaps a new API in the future?). In this sense the directory is helpful because prevents connections before it reaches the server socket. OTOH it will introduce some small difference between Unix and Windows in at least some (mis)configurations:

  1. Assume same user for all steps;
  2. Create server without current user only flag;
  3. Create client with current user only flag;

On Windows: client will connect fine;

On Unix: client, depending on API, hangs or throws timeout exception (since the path for the named pipes is different between server and client)

All of this to say that let's keep the directory, but also add the getpeerid check also on the server side (extra precaution), it is already done on the client side.

<Reference Include="Microsoft.Win32.Primitives" />
<Reference Include="System.IO.FileSystem" />
<Reference Include="System.Net.Primitives" />
<Reference Include="System.Net.Sockets" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 ----
// -----------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}

Expand All @@ -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 ----
// -----------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;

namespace System.IO.Pipes
{
Expand Down Expand Up @@ -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);
}
Expand All @@ -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;
Expand Down
Loading