< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.Channels.PipeSharedMemory
Assembly: CoreWCF.NetNamedPipe
File(s): /home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.NetNamedPipe/src/CoreWCF/Channels/PipeHelpers.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 81
Coverable lines: 81
Total lines: 464
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 26
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%110%
.ctor(...)100%110%
Create(...)0%220%
TryCreate(...)0%12120%
InitializeContents(...)100%110%
CreatePipeNameInUseException(...)100%110%
GetView(...)0%440%
Dispose()0%220%
CreatePipeNameCannotBeAccessedException(...)100%110%
BuildPipeName(...)0%220%

File(s)

/home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.NetNamedPipe/src/CoreWCF/Channels/PipeHelpers.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Runtime.InteropServices;
 7using System.Security.Cryptography;
 8using System.Text;
 9using System.ComponentModel;
 10using System.Security.Principal;
 11using System.Security.AccessControl;
 12using CoreWCF.IO;
 13using System.Globalization;
 14using System.Threading;
 15using CoreWCF.Security;
 16using Microsoft.Extensions.Logging;
 17using System.Runtime.Versioning;
 18
 19namespace CoreWCF.Channels
 20{
 21    // Handles manipulating the net.pipe uri to a predictable shared memory path. If the path is too long,
 22    // it's shortened by using a hash algorithm. This matches the algorithm the client uses to find
 23    // the shared memory path. The shared memory will then contain the actual named pipe name which is
 24    // unique from one run to the next.
 25    [SupportedOSPlatform("windows")]
 26    internal static class PipeUri
 27    {
 28        public static void Validate(Uri uri)
 29        {
 30            if (uri.Scheme != Uri.UriSchemeNetPipe)
 31                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(nameof(uri), SR.PipeUriSchemeWrong);
 32        }
 33
 34        public static string BuildSharedMemoryName(Uri uri, HostNameComparisonMode hostNameComparisonMode, bool global)
 35        {
 36            string path = GetPath(uri);
 37            string host = null;
 38
 39            switch (hostNameComparisonMode)
 40            {
 41                case HostNameComparisonMode.StrongWildcard:
 42                    host = "+";
 43                    break;
 44                case HostNameComparisonMode.Exact:
 45                    host = uri.Host;
 46                    break;
 47                case HostNameComparisonMode.WeakWildcard:
 48                    host = "*";
 49                    break;
 50            }
 51
 52            return BuildSharedMemoryName(host, path, global);
 53        }
 54
 55        private static string BuildSharedMemoryName(string hostName, string path, bool global)
 56        {
 57            StringBuilder builder = new StringBuilder();
 58            builder.Append(Uri.UriSchemeNetPipe);
 59            builder.Append("://");
 60            builder.Append(hostName.ToUpperInvariant());
 61            builder.Append(path);
 62            string canonicalName = builder.ToString();
 63
 64            byte[] canonicalBytes = Encoding.UTF8.GetBytes(canonicalName);
 65            byte[] hashedBytes;
 66            string separator;
 67
 68            if (canonicalBytes.Length >= 128)
 69            {
 70                using (HashAlgorithm hash = GetHashAlgorithm())
 71                {
 72                    hashedBytes = hash.ComputeHash(canonicalBytes);
 73                }
 74                separator = ":H";
 75            }
 76            else
 77            {
 78                hashedBytes = canonicalBytes;
 79                separator = ":E";
 80            }
 81
 82            builder = new StringBuilder();
 83            if (global)
 84            {
 85                // we may need to create the shared memory in the global namespace so we work with terminal services+adm
 86                builder.Append(@"Global\");
 87            }
 88            else
 89            {
 90                builder.Append(@"Local\");
 91            }
 92            builder.Append(Uri.UriSchemeNetPipe);
 93            builder.Append(separator);
 94            builder.Append(Convert.ToBase64String(hashedBytes));
 95            return builder.ToString();
 96        }
 97
 98        public static string GetPath(Uri uri)
 99        {
 100            string path = uri.LocalPath.ToUpperInvariant();
 101            if (!path.EndsWith("/", StringComparison.Ordinal))
 102                path = path + "/";
 103            return path;
 104        }
 105
 106        internal const string UseSha1InPipeConnectionGetHashAlgorithmString = "Switch.System.ServiceModel.UseSha1InPipeC
 107        internal static bool s_useSha1InPipeConnectionGetHashAlgorithm = AppContext.TryGetSwitch(UseSha1InPipeConnection
 108
 109        private static HashAlgorithm GetHashAlgorithm()
 110        {
 111            if (s_useSha1InPipeConnectionGetHashAlgorithm)
 112            {
 113                return SHA1.Create();
 114            }
 115            else
 116            {
 117                return SHA256.Create();
 118            }
 119        }
 120    }
 121
 122    // This class handles creating a shared memory object which holds the guid of the actual
 123    // named pipe that clients will connect to.
 124    [SupportedOSPlatform("windows")]
 125    internal unsafe class PipeSharedMemory : IDisposable
 126    {
 127        internal const string PipePrefix = @"\\.\pipe\";
 128        internal const string PipeLocalPrefix = @"\\.\pipe\Local\";
 129        private SafeFileMappingHandle _fileMapping;
 130        private string _pipeName;
 131        private string _pipeNameGuidPart;
 132        private readonly Uri _pipeUri;
 133
 134        private PipeSharedMemory(SafeFileMappingHandle fileMapping, Uri pipeUri)
 0135            : this(fileMapping, pipeUri, null)
 136        {
 0137        }
 138
 0139        private PipeSharedMemory(SafeFileMappingHandle fileMapping, Uri pipeUri, string pipeName)
 140        {
 0141            _pipeName = pipeName;
 0142            _fileMapping = fileMapping;
 0143            _pipeUri = pipeUri;
 0144        }
 145
 146        public static PipeSharedMemory Create(List<SecurityIdentifier> allowedSids, Uri pipeUri, string sharedMemoryName
 147        {
 148            PipeSharedMemory result;
 0149            if (TryCreate(allowedSids, pipeUri, sharedMemoryName, logger, out result))
 150            {
 0151                return result;
 152            }
 153            else
 154            {
 0155                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(CreatePipeNameInUseException(UnsafeNativeMetho
 156            }
 157        }
 158
 159        public static bool TryCreate(List<SecurityIdentifier> allowedSids, Uri pipeUri, string sharedMemoryName, ILogger
 160        {
 0161            Guid pipeGuid = Guid.NewGuid();
 0162            string pipeName = BuildPipeName(pipeGuid.ToString());
 163            byte[] binarySecurityDescriptor;
 164            try
 165            {
 0166                binarySecurityDescriptor = SecurityDescriptorHelper.FromSecurityIdentifiers(allowedSids, UnsafeNativeMet
 0167            }
 0168            catch (Win32Exception e)
 169            {
 170                // While Win32exceptions are not expected, if they do occur we need to obey the pipe/communication excep
 0171                Exception innerException = new PipeException(e.Message, e);
 0172                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CommunicationException(innerException.Mess
 173            }
 174
 175            SafeFileMappingHandle fileMapping;
 176            int error;
 0177            result = null;
 0178            fixed (byte* pinnedSecurityDescriptor = binarySecurityDescriptor)
 179            {
 0180                UnsafeNativeMethods.SECURITY_ATTRIBUTES securityAttributes = new UnsafeNativeMethods.SECURITY_ATTRIBUTES
 0181                securityAttributes.lpSecurityDescriptor = (IntPtr)pinnedSecurityDescriptor;
 182
 0183                fileMapping = UnsafeNativeMethods.CreateFileMapping((IntPtr)(-1), securityAttributes,
 0184                    UnsafeNativeMethods.PAGE_READWRITE, 0, sizeof(SharedMemoryContents), sharedMemoryName);
 0185                error = Marshal.GetLastWin32Error();
 186            }
 187
 0188            if (fileMapping.IsInvalid)
 189            {
 0190                fileMapping.SetHandleAsInvalid();
 0191                if (error == UnsafeNativeMethods.ERROR_ACCESS_DENIED)
 192                {
 0193                    return false;
 194                }
 195                else
 196                {
 0197                    Exception innerException = new PipeException(SR.Format(SR.PipeNameCantBeReserved,
 0198                        pipeUri.AbsoluteUri, PipeError.GetErrorString(error)), error);
 0199                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new AddressAccessDeniedException(innerExce
 200                }
 201            }
 202
 203            // now we have a valid file mapping handle
 0204            if (error == UnsafeNativeMethods.ERROR_ALREADY_EXISTS)
 205            {
 0206                fileMapping.Close();
 0207                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(CreatePipeNameInUseException(error, pipeUri));
 208            }
 209
 0210            PipeSharedMemory pipeSharedMemory = new PipeSharedMemory(fileMapping, pipeUri, pipeName);
 0211            bool disposeSharedMemory = true;
 212            try
 213            {
 0214                pipeSharedMemory.InitializeContents(pipeGuid);
 0215                disposeSharedMemory = false;
 0216                result = pipeSharedMemory;
 217
 218                //if (TD.PipeSharedMemoryCreatedIsEnabled())
 219                //{
 220                //    TD.PipeSharedMemoryCreated(sharedMemoryName);
 221                //}
 0222                return true;
 223            }
 224            finally
 225            {
 0226                if (disposeSharedMemory)
 227                {
 0228                    pipeSharedMemory.Dispose();
 229                }
 0230            }
 0231        }
 232
 233        private void InitializeContents(Guid pipeGuid)
 234        {
 0235            SafeViewOfFileHandle view = GetView(true);
 236            try
 237            {
 0238                SharedMemoryContents* contents = (SharedMemoryContents*)view.DangerousGetHandle();
 0239                contents->pipeGuid = pipeGuid;
 0240                Thread.MemoryBarrier();
 0241                contents->isInitialized = true;
 0242            }
 243            finally
 244            {
 0245                view.Close();
 0246            }
 0247        }
 248
 249        public static Exception CreatePipeNameInUseException(int error, Uri pipeUri)
 250        {
 0251            Exception innerException = new PipeException(SR.Format(SR.PipeNameInUse, pipeUri.AbsoluteUri), error);
 0252            return new AddressAlreadyInUseException(innerException.Message, innerException);
 253        }
 254
 255        private SafeViewOfFileHandle GetView(bool writable)
 256        {
 0257            SafeViewOfFileHandle handle = UnsafeNativeMethods.MapViewOfFile(_fileMapping,
 0258                writable ? UnsafeNativeMethods.FILE_MAP_WRITE : UnsafeNativeMethods.FILE_MAP_READ,
 0259                0, 0, (IntPtr)sizeof(SharedMemoryContents));
 0260            if (handle.IsInvalid)
 261            {
 0262                int error = Marshal.GetLastWin32Error();
 0263                handle.SetHandleAsInvalid();
 0264                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(CreatePipeNameCannotBeAccessedException(error,
 265            }
 0266            return handle;
 267        }
 268
 269        public void Dispose()
 270        {
 0271            if (_fileMapping != null)
 272            {
 0273                _fileMapping.Dispose();
 0274                _fileMapping = null;
 275            }
 0276        }
 277
 278        public string PipeName
 279        {
 280            get
 281            {
 0282                if (_pipeName == null)
 283                {
 0284                    SafeViewOfFileHandle view = GetView(false);
 285                    try
 286                    {
 0287                        SharedMemoryContents* contents = (SharedMemoryContents*)view.DangerousGetHandle();
 0288                        if (contents->isInitialized)
 289                        {
 0290                            Thread.MemoryBarrier();
 0291                            _pipeNameGuidPart = contents->pipeGuid.ToString();
 0292                            _pipeName = BuildPipeName(_pipeNameGuidPart);
 293                        }
 0294                    }
 295                    finally
 296                    {
 0297                        view.Close();
 0298                    }
 299                }
 300
 0301                return _pipeName;
 302            }
 303        }
 304
 305        private static Exception CreatePipeNameCannotBeAccessedException(int error, Uri pipeUri)
 306        {
 0307            Exception innerException = new PipeException(SR.Format(SR.PipeNameCanNotBeAccessed, PipeError.GetErrorString
 0308            return new AddressAccessDeniedException(SR.Format(SR.PipeNameCanNotBeAccessed2, pipeUri.AbsoluteUri), innerE
 309        }
 310
 311        private static string BuildPipeName(string pipeGuid)
 312        {
 0313            return (AppContainerInfo.IsRunningInAppContainer ? PipeLocalPrefix : PipePrefix) + pipeGuid;
 314        }
 315
 316        [StructLayout(LayoutKind.Sequential)]
 317        private struct SharedMemoryContents
 318        {
 319            public bool isInitialized;
 320            public Guid pipeGuid;
 321        }
 322    }
 323
 324    internal static class PipeError
 325    {
 326        public static string GetErrorString(int error)
 327        {
 328            StringBuilder stringBuilder = new StringBuilder(512);
 329            if (UnsafeNativeMethods.FormatMessage(UnsafeNativeMethods.FORMAT_MESSAGE_IGNORE_INSERTS |
 330                UnsafeNativeMethods.FORMAT_MESSAGE_FROM_SYSTEM | UnsafeNativeMethods.FORMAT_MESSAGE_ARGUMENT_ARRAY,
 331                IntPtr.Zero, error, CultureInfo.CurrentCulture.LCID, stringBuilder, stringBuilder.Capacity, IntPtr.Zero)
 332            {
 333                stringBuilder = stringBuilder.Replace("\n", "");
 334                stringBuilder = stringBuilder.Replace("\r", "");
 335                return SR.Format(
 336                    SR.PipeKnownWin32Error,
 337                    stringBuilder.ToString(),
 338                    error.ToString(CultureInfo.InvariantCulture),
 339                    Convert.ToString(error, 16));
 340            }
 341            else
 342            {
 343                return SR.Format(
 344                    SR.PipeUnknownWin32Error,
 345                    error.ToString(CultureInfo.InvariantCulture),
 346                    Convert.ToString(error, 16));
 347            }
 348        }
 349    }
 350
 351    [SupportedOSPlatform("windows")]
 352    internal static class SecurityDescriptorHelper
 353    {
 354        private static byte[] s_worldCreatorOwnerWithReadAndWriteDescriptorDenyNetwork;
 355        private static byte[] GetWorldCreatorOwnerWithReadAndWriteDescriptorDenyNetwork(ILogger logger)
 356        {
 357            if (s_worldCreatorOwnerWithReadAndWriteDescriptorDenyNetwork == null)
 358            {
 359                s_worldCreatorOwnerWithReadAndWriteDescriptorDenyNetwork = FromSecurityIdentifiersFull(null, UnsafeNativ
 360            }
 361
 362            return s_worldCreatorOwnerWithReadAndWriteDescriptorDenyNetwork;
 363        }
 364
 365        private static byte[] s_worldCreatorOwnerWithReadDescriptorDenyNetwork;
 366        private static byte[] GetWorldCreatorOwnerWithReadDescriptorDenyNetwork(ILogger logger)
 367        {
 368            if (s_worldCreatorOwnerWithReadDescriptorDenyNetwork == null)
 369            {
 370                s_worldCreatorOwnerWithReadDescriptorDenyNetwork = FromSecurityIdentifiersFull(null, UnsafeNativeMethods
 371            }
 372
 373            return s_worldCreatorOwnerWithReadDescriptorDenyNetwork;
 374        }
 375
 376        internal static byte[] FromSecurityIdentifiers(List<SecurityIdentifier> allowedSids, int accessRights, ILogger l
 377        {
 378            if (allowedSids == null)
 379            {
 380                if (accessRights == (UnsafeNativeMethods.GENERIC_READ | UnsafeNativeMethods.GENERIC_WRITE))
 381                {
 382                    return GetWorldCreatorOwnerWithReadAndWriteDescriptorDenyNetwork(logger);
 383                }
 384
 385                if (accessRights == UnsafeNativeMethods.GENERIC_READ)
 386                {
 387                    return GetWorldCreatorOwnerWithReadDescriptorDenyNetwork(logger);
 388                }
 389            }
 390
 391            return FromSecurityIdentifiersFull(allowedSids, accessRights, logger);
 392        }
 393
 394        private static byte[] FromSecurityIdentifiersFull(List<SecurityIdentifier> allowedSids, int accessRights, ILogge
 395        {
 396            int capacity = allowedSids == null ? 3 : 2 + allowedSids.Count;
 397            DiscretionaryAcl dacl = new DiscretionaryAcl(false, false, capacity);
 398
 399            // add deny ACE first so that we don't get short circuited
 400            dacl.AddAccess(AccessControlType.Deny, new SecurityIdentifier(WellKnownSidType.NetworkSid, null),
 401                UnsafeNativeMethods.GENERIC_ALL, InheritanceFlags.None, PropagationFlags.None);
 402
 403            // clients get different rights, since they shouldn't be able to listen
 404            int clientAccessRights = GenerateClientAccessRights(accessRights);
 405
 406            if (allowedSids == null)
 407            {
 408                logger.LogDebug("Adding default ACL allow WellKnownSidType.WorldSid to security identifiers");
 409                dacl.AddAccess(AccessControlType.Allow, new SecurityIdentifier(WellKnownSidType.WorldSid, null),
 410                    clientAccessRights, InheritanceFlags.None, PropagationFlags.None);
 411            }
 412            else
 413            {
 414                for (int i = 0; i < allowedSids.Count; i++)
 415                {
 416                    SecurityIdentifier allowedSid = allowedSids[i];
 417                    logger.LogDebug("Adding ACL allow SID {allowedSid} to security identifiers", allowedSid);
 418                    dacl.AddAccess(AccessControlType.Allow, allowedSid,
 419                        clientAccessRights, InheritanceFlags.None, PropagationFlags.None);
 420                }
 421            }
 422
 423            var processLogonSid = SecurityUtils.GetProcessLogonSid();
 424            logger.LogDebug("Adding ACL allow Process Logon SID {processLogonSid} to security identifiers", processLogon
 425            dacl.AddAccess(AccessControlType.Allow, processLogonSid, accessRights, InheritanceFlags.None, PropagationFla
 426
 427            if (AppContainerInfo.IsRunningInAppContainer)
 428            {
 429                // NamedPipeBinding requires dacl with current AppContainer SID
 430                // to setup multiple NamedPipes in the BeginAccept loop.
 431                var appContainerSid = AppContainerInfo.GetCurrentAppContainerSid();
 432                logger.LogDebug("Adding ACL allow App Container SID {appContainerSid} to security identifiers", appConta
 433                dacl.AddAccess(AccessControlType.Allow, appContainerSid, accessRights, InheritanceFlags.None, Propagatio
 434            }
 435
 436            CommonSecurityDescriptor securityDescriptor =
 437                new CommonSecurityDescriptor(false, false, ControlFlags.None, null, null, null, dacl);
 438            byte[] binarySecurityDescriptor = new byte[securityDescriptor.BinaryLength];
 439            securityDescriptor.GetBinaryForm(binarySecurityDescriptor, 0);
 440            return binarySecurityDescriptor;
 441        }
 442
 443        // Security: We cannot grant rights for FILE_CREATE_PIPE_INSTANCE to clients, otherwise other apps can intercept
 444        // FILE_CREATE_PIPE_INSTANCE is granted in 2 ways, via GENERIC_WRITE or directly specified. Remove both.
 445        private static int GenerateClientAccessRights(int accessRights)
 446        {
 447            int everyoneAccessRights = accessRights;
 448
 449            if ((everyoneAccessRights & UnsafeNativeMethods.GENERIC_WRITE) != 0)
 450            {
 451                everyoneAccessRights &= ~UnsafeNativeMethods.GENERIC_WRITE;
 452
 453                // Since GENERIC_WRITE grants the permissions to write to a file, we need to add it back.
 454                const int clientWriteAccess = UnsafeNativeMethods.FILE_WRITE_ATTRIBUTES | UnsafeNativeMethods.FILE_WRITE
 455                everyoneAccessRights |= clientWriteAccess;
 456            }
 457
 458            // Future proofing: FILE_CREATE_PIPE_INSTANCE isn't used currently but we need to ensure it is not granted.
 459            everyoneAccessRights &= ~UnsafeNativeMethods.FILE_CREATE_PIPE_INSTANCE;
 460
 461            return everyoneAccessRights;
 462        }
 463    }
 464}