|
// 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;
using System.IO;
using System.IO.Pipes;
using System.Security.AccessControl;
using System.Security.Principal;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
/// <summary>
/// The compiler needs to take advantage of features on named pipes which require target framework
/// specific APIs. This class is meant to provide a simple, universal interface on top of the
/// multi-targeting code that is needed here.
/// </summary>
internal static class NamedPipeUtil
{
// Size of the buffers to use: 64K
private const int PipeBufferSize = 0x10000;
private static string GetPipeNameOrPath(string pipeName)
{
if (PlatformInformation.IsUnix)
{
// If we're on a Unix machine then named pipes are implemented using Unix Domain Sockets.
// Most Unix systems have a maximum path length limit for Unix Domain Sockets, with
// Mac having a particularly short one. Mac also has a generated temp directory that
// can be quite long, leaving very little room for the actual pipe name. Fortunately,
// '/tmp' is mandated by POSIX to always be a valid temp directory, so we can use that
// instead.
return Path.Combine("/tmp", pipeName);
}
else
{
return pipeName;
}
}
/// <summary>
/// Create a client for the current user only.
/// </summary>
internal static NamedPipeClientStream CreateClient(string serverName, string pipeName, PipeDirection direction, PipeOptions options)
=> new NamedPipeClientStream(serverName, GetPipeNameOrPath(pipeName), direction, options | CurrentUserOption);
/// <summary>
/// Does the client of "pipeStream" have the same identity and elevation as we do? The <see cref="CreateClient"/> and
/// <see cref="CreateServer(string, PipeDirection?)" /> methods will already guarantee that the identity of the client and server are the
/// same. This method is attempting to validate that the elevation level is the same between both ends of the
/// named pipe (want to disallow low priv session sending compilation requests to an elevated one).
/// </summary>
internal static bool CheckClientElevationMatches(NamedPipeServerStream pipeStream)
{
if (PlatformInformation.IsWindows)
{
#pragma warning disable CA1416 // Validate platform compatibility
var serverIdentity = getIdentity();
(string name, bool admin) clientIdentity = default;
pipeStream.RunAsClient(() => { clientIdentity = getIdentity(); });
return
StringComparer.OrdinalIgnoreCase.Equals(serverIdentity.name, clientIdentity.name) &&
serverIdentity.admin == clientIdentity.admin;
(string name, bool admin) getIdentity()
{
var currentIdentity = WindowsIdentity.GetCurrent();
var currentPrincipal = new WindowsPrincipal(currentIdentity);
var elevatedToAdmin = currentPrincipal.IsInRole(WindowsBuiltInRole.Administrator);
return (currentIdentity.Name, elevatedToAdmin);
}
#pragma warning restore CA1416 // Validate platform compatibility
}
return true;
}
/// <summary>
/// Create a server for the current user only
/// </summary>
internal static NamedPipeServerStream CreateServer(string pipeName, PipeDirection? pipeDirection = null)
{
var pipeOptions = PipeOptions.Asynchronous | PipeOptions.WriteThrough;
return CreateServer(
pipeName,
pipeDirection ?? PipeDirection.InOut,
NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Byte,
pipeOptions,
PipeBufferSize,
PipeBufferSize);
}
#if NET472
const int s_currentUserOnlyValue = 0x20000000;
/// <summary>
/// Mono supports CurrentUserOnly even though it's not exposed on the reference assemblies for net472. This
/// must be used because ACL security does not work.
/// </summary>
private static readonly PipeOptions CurrentUserOption = PlatformInformation.IsRunningOnMono
? (PipeOptions)s_currentUserOnlyValue
: PipeOptions.None;
private static NamedPipeServerStream CreateServer(
string pipeName,
PipeDirection direction,
int maxNumberOfServerInstances,
PipeTransmissionMode transmissionMode,
PipeOptions options,
int inBufferSize,
int outBufferSize) =>
new NamedPipeServerStream(
GetPipeNameOrPath(pipeName),
direction,
maxNumberOfServerInstances,
transmissionMode,
options | CurrentUserOption,
inBufferSize,
outBufferSize,
CreatePipeSecurity(),
HandleInheritability.None);
/// <summary>
/// Check to ensure that the named pipe server we connected to is owned by the same
/// user.
/// </summary>
internal static bool CheckPipeConnectionOwnership(NamedPipeClientStream pipeStream)
{
if (PlatformInformation.IsWindows)
{
var currentIdentity = WindowsIdentity.GetCurrent();
var currentOwner = currentIdentity.Owner;
var remotePipeSecurity = pipeStream.GetAccessControl();
var remoteOwner = remotePipeSecurity.GetOwner(typeof(SecurityIdentifier));
return currentOwner.Equals(remoteOwner);
}
// Client side validation isn't supported on Unix. The model relies on the server side
// security here.
return true;
}
internal static PipeSecurity? CreatePipeSecurity()
{
if (PlatformInformation.IsRunningOnMono)
{
// Pipe security and additional access rights constructor arguments
// are not supported by Mono
// https://github.com/dotnet/roslyn/pull/30810
// https://github.com/mono/mono/issues/11406
return null;
}
var security = new PipeSecurity();
SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner;
// Restrict access to just this account.
PipeAccessRule rule = new PipeAccessRule(identifier, PipeAccessRights.ReadWrite | PipeAccessRights.CreateNewInstance, AccessControlType.Allow);
security.AddAccessRule(rule);
security.SetOwner(identifier);
return security;
}
#elif NETCOREAPP
private const PipeOptions CurrentUserOption = PipeOptions.CurrentUserOnly;
// Validation is handled by CurrentUserOnly
#pragma warning disable IDE0060 // Remove unused parameter
internal static bool CheckPipeConnectionOwnership(NamedPipeClientStream pipeStream) => true;
#pragma warning restore IDE0060 // Remove unused parameter
// Validation is handled by CurrentUserOnly
internal static PipeSecurity? CreatePipeSecurity() => null;
private static NamedPipeServerStream CreateServer(
string pipeName,
PipeDirection direction,
int maxNumberOfServerInstances,
PipeTransmissionMode transmissionMode,
PipeOptions options,
int inBufferSize,
int outBufferSize) =>
new NamedPipeServerStream(
GetPipeNameOrPath(pipeName),
direction,
maxNumberOfServerInstances,
transmissionMode,
options | CurrentUserOption,
inBufferSize,
outBufferSize);
#else
#error Unsupported configuration
#endif
}
}
|