< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.Channels.SecurityDescriptorHelper
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: 44
Coverable lines: 44
Total lines: 464
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 20
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

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)
 135            : this(fileMapping, pipeUri, null)
 136        {
 137        }
 138
 139        private PipeSharedMemory(SafeFileMappingHandle fileMapping, Uri pipeUri, string pipeName)
 140        {
 141            _pipeName = pipeName;
 142            _fileMapping = fileMapping;
 143            _pipeUri = pipeUri;
 144        }
 145
 146        public static PipeSharedMemory Create(List<SecurityIdentifier> allowedSids, Uri pipeUri, string sharedMemoryName
 147        {
 148            PipeSharedMemory result;
 149            if (TryCreate(allowedSids, pipeUri, sharedMemoryName, logger, out result))
 150            {
 151                return result;
 152            }
 153            else
 154            {
 155                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(CreatePipeNameInUseException(UnsafeNativeMetho
 156            }
 157        }
 158
 159        public static bool TryCreate(List<SecurityIdentifier> allowedSids, Uri pipeUri, string sharedMemoryName, ILogger
 160        {
 161            Guid pipeGuid = Guid.NewGuid();
 162            string pipeName = BuildPipeName(pipeGuid.ToString());
 163            byte[] binarySecurityDescriptor;
 164            try
 165            {
 166                binarySecurityDescriptor = SecurityDescriptorHelper.FromSecurityIdentifiers(allowedSids, UnsafeNativeMet
 167            }
 168            catch (Win32Exception e)
 169            {
 170                // While Win32exceptions are not expected, if they do occur we need to obey the pipe/communication excep
 171                Exception innerException = new PipeException(e.Message, e);
 172                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CommunicationException(innerException.Mess
 173            }
 174
 175            SafeFileMappingHandle fileMapping;
 176            int error;
 177            result = null;
 178            fixed (byte* pinnedSecurityDescriptor = binarySecurityDescriptor)
 179            {
 180                UnsafeNativeMethods.SECURITY_ATTRIBUTES securityAttributes = new UnsafeNativeMethods.SECURITY_ATTRIBUTES
 181                securityAttributes.lpSecurityDescriptor = (IntPtr)pinnedSecurityDescriptor;
 182
 183                fileMapping = UnsafeNativeMethods.CreateFileMapping((IntPtr)(-1), securityAttributes,
 184                    UnsafeNativeMethods.PAGE_READWRITE, 0, sizeof(SharedMemoryContents), sharedMemoryName);
 185                error = Marshal.GetLastWin32Error();
 186            }
 187
 188            if (fileMapping.IsInvalid)
 189            {
 190                fileMapping.SetHandleAsInvalid();
 191                if (error == UnsafeNativeMethods.ERROR_ACCESS_DENIED)
 192                {
 193                    return false;
 194                }
 195                else
 196                {
 197                    Exception innerException = new PipeException(SR.Format(SR.PipeNameCantBeReserved,
 198                        pipeUri.AbsoluteUri, PipeError.GetErrorString(error)), error);
 199                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new AddressAccessDeniedException(innerExce
 200                }
 201            }
 202
 203            // now we have a valid file mapping handle
 204            if (error == UnsafeNativeMethods.ERROR_ALREADY_EXISTS)
 205            {
 206                fileMapping.Close();
 207                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(CreatePipeNameInUseException(error, pipeUri));
 208            }
 209
 210            PipeSharedMemory pipeSharedMemory = new PipeSharedMemory(fileMapping, pipeUri, pipeName);
 211            bool disposeSharedMemory = true;
 212            try
 213            {
 214                pipeSharedMemory.InitializeContents(pipeGuid);
 215                disposeSharedMemory = false;
 216                result = pipeSharedMemory;
 217
 218                //if (TD.PipeSharedMemoryCreatedIsEnabled())
 219                //{
 220                //    TD.PipeSharedMemoryCreated(sharedMemoryName);
 221                //}
 222                return true;
 223            }
 224            finally
 225            {
 226                if (disposeSharedMemory)
 227                {
 228                    pipeSharedMemory.Dispose();
 229                }
 230            }
 231        }
 232
 233        private void InitializeContents(Guid pipeGuid)
 234        {
 235            SafeViewOfFileHandle view = GetView(true);
 236            try
 237            {
 238                SharedMemoryContents* contents = (SharedMemoryContents*)view.DangerousGetHandle();
 239                contents->pipeGuid = pipeGuid;
 240                Thread.MemoryBarrier();
 241                contents->isInitialized = true;
 242            }
 243            finally
 244            {
 245                view.Close();
 246            }
 247        }
 248
 249        public static Exception CreatePipeNameInUseException(int error, Uri pipeUri)
 250        {
 251            Exception innerException = new PipeException(SR.Format(SR.PipeNameInUse, pipeUri.AbsoluteUri), error);
 252            return new AddressAlreadyInUseException(innerException.Message, innerException);
 253        }
 254
 255        private SafeViewOfFileHandle GetView(bool writable)
 256        {
 257            SafeViewOfFileHandle handle = UnsafeNativeMethods.MapViewOfFile(_fileMapping,
 258                writable ? UnsafeNativeMethods.FILE_MAP_WRITE : UnsafeNativeMethods.FILE_MAP_READ,
 259                0, 0, (IntPtr)sizeof(SharedMemoryContents));
 260            if (handle.IsInvalid)
 261            {
 262                int error = Marshal.GetLastWin32Error();
 263                handle.SetHandleAsInvalid();
 264                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(CreatePipeNameCannotBeAccessedException(error,
 265            }
 266            return handle;
 267        }
 268
 269        public void Dispose()
 270        {
 271            if (_fileMapping != null)
 272            {
 273                _fileMapping.Dispose();
 274                _fileMapping = null;
 275            }
 276        }
 277
 278        public string PipeName
 279        {
 280            get
 281            {
 282                if (_pipeName == null)
 283                {
 284                    SafeViewOfFileHandle view = GetView(false);
 285                    try
 286                    {
 287                        SharedMemoryContents* contents = (SharedMemoryContents*)view.DangerousGetHandle();
 288                        if (contents->isInitialized)
 289                        {
 290                            Thread.MemoryBarrier();
 291                            _pipeNameGuidPart = contents->pipeGuid.ToString();
 292                            _pipeName = BuildPipeName(_pipeNameGuidPart);
 293                        }
 294                    }
 295                    finally
 296                    {
 297                        view.Close();
 298                    }
 299                }
 300
 301                return _pipeName;
 302            }
 303        }
 304
 305        private static Exception CreatePipeNameCannotBeAccessedException(int error, Uri pipeUri)
 306        {
 307            Exception innerException = new PipeException(SR.Format(SR.PipeNameCanNotBeAccessed, PipeError.GetErrorString
 308            return new AddressAccessDeniedException(SR.Format(SR.PipeNameCanNotBeAccessed2, pipeUri.AbsoluteUri), innerE
 309        }
 310
 311        private static string BuildPipeName(string pipeGuid)
 312        {
 313            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        {
 0357            if (s_worldCreatorOwnerWithReadAndWriteDescriptorDenyNetwork == null)
 358            {
 0359                s_worldCreatorOwnerWithReadAndWriteDescriptorDenyNetwork = FromSecurityIdentifiersFull(null, UnsafeNativ
 360            }
 361
 0362            return s_worldCreatorOwnerWithReadAndWriteDescriptorDenyNetwork;
 363        }
 364
 365        private static byte[] s_worldCreatorOwnerWithReadDescriptorDenyNetwork;
 366        private static byte[] GetWorldCreatorOwnerWithReadDescriptorDenyNetwork(ILogger logger)
 367        {
 0368            if (s_worldCreatorOwnerWithReadDescriptorDenyNetwork == null)
 369            {
 0370                s_worldCreatorOwnerWithReadDescriptorDenyNetwork = FromSecurityIdentifiersFull(null, UnsafeNativeMethods
 371            }
 372
 0373            return s_worldCreatorOwnerWithReadDescriptorDenyNetwork;
 374        }
 375
 376        internal static byte[] FromSecurityIdentifiers(List<SecurityIdentifier> allowedSids, int accessRights, ILogger l
 377        {
 0378            if (allowedSids == null)
 379            {
 0380                if (accessRights == (UnsafeNativeMethods.GENERIC_READ | UnsafeNativeMethods.GENERIC_WRITE))
 381                {
 0382                    return GetWorldCreatorOwnerWithReadAndWriteDescriptorDenyNetwork(logger);
 383                }
 384
 0385                if (accessRights == UnsafeNativeMethods.GENERIC_READ)
 386                {
 0387                    return GetWorldCreatorOwnerWithReadDescriptorDenyNetwork(logger);
 388                }
 389            }
 390
 0391            return FromSecurityIdentifiersFull(allowedSids, accessRights, logger);
 392        }
 393
 394        private static byte[] FromSecurityIdentifiersFull(List<SecurityIdentifier> allowedSids, int accessRights, ILogge
 395        {
 0396            int capacity = allowedSids == null ? 3 : 2 + allowedSids.Count;
 0397            DiscretionaryAcl dacl = new DiscretionaryAcl(false, false, capacity);
 398
 399            // add deny ACE first so that we don't get short circuited
 0400            dacl.AddAccess(AccessControlType.Deny, new SecurityIdentifier(WellKnownSidType.NetworkSid, null),
 0401                UnsafeNativeMethods.GENERIC_ALL, InheritanceFlags.None, PropagationFlags.None);
 402
 403            // clients get different rights, since they shouldn't be able to listen
 0404            int clientAccessRights = GenerateClientAccessRights(accessRights);
 405
 0406            if (allowedSids == null)
 407            {
 0408                logger.LogDebug("Adding default ACL allow WellKnownSidType.WorldSid to security identifiers");
 0409                dacl.AddAccess(AccessControlType.Allow, new SecurityIdentifier(WellKnownSidType.WorldSid, null),
 0410                    clientAccessRights, InheritanceFlags.None, PropagationFlags.None);
 411            }
 412            else
 413            {
 0414                for (int i = 0; i < allowedSids.Count; i++)
 415                {
 0416                    SecurityIdentifier allowedSid = allowedSids[i];
 0417                    logger.LogDebug("Adding ACL allow SID {allowedSid} to security identifiers", allowedSid);
 0418                    dacl.AddAccess(AccessControlType.Allow, allowedSid,
 0419                        clientAccessRights, InheritanceFlags.None, PropagationFlags.None);
 420                }
 421            }
 422
 0423            var processLogonSid = SecurityUtils.GetProcessLogonSid();
 0424            logger.LogDebug("Adding ACL allow Process Logon SID {processLogonSid} to security identifiers", processLogon
 0425            dacl.AddAccess(AccessControlType.Allow, processLogonSid, accessRights, InheritanceFlags.None, PropagationFla
 426
 0427            if (AppContainerInfo.IsRunningInAppContainer)
 428            {
 429                // NamedPipeBinding requires dacl with current AppContainer SID
 430                // to setup multiple NamedPipes in the BeginAccept loop.
 0431                var appContainerSid = AppContainerInfo.GetCurrentAppContainerSid();
 0432                logger.LogDebug("Adding ACL allow App Container SID {appContainerSid} to security identifiers", appConta
 0433                dacl.AddAccess(AccessControlType.Allow, appContainerSid, accessRights, InheritanceFlags.None, Propagatio
 434            }
 435
 0436            CommonSecurityDescriptor securityDescriptor =
 0437                new CommonSecurityDescriptor(false, false, ControlFlags.None, null, null, null, dacl);
 0438            byte[] binarySecurityDescriptor = new byte[securityDescriptor.BinaryLength];
 0439            securityDescriptor.GetBinaryForm(binarySecurityDescriptor, 0);
 0440            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        {
 0447            int everyoneAccessRights = accessRights;
 448
 0449            if ((everyoneAccessRights & UnsafeNativeMethods.GENERIC_WRITE) != 0)
 450            {
 0451                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
 0455                everyoneAccessRights |= clientWriteAccess;
 456            }
 457
 458            // Future proofing: FILE_CREATE_PIPE_INSTANCE isn't used currently but we need to ensure it is not granted.
 0459            everyoneAccessRights &= ~UnsafeNativeMethods.FILE_CREATE_PIPE_INSTANCE;
 460
 0461            return everyoneAccessRights;
 462        }
 463    }
 464}