public void GetAccessControl_NamedPipe_BeforeWaitingToConnect()
        {
            string pipeName = GetUniquePipeName();
            var server = new NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
            var client = new NamedPipeClientStream(".", pipeName, PipeDirection.In, PipeOptions.Asynchronous);

            Assert.NotNull(server.GetAccessControl());
            server.SetAccessControl(new PipeSecurity());
            Assert.Throws<InvalidOperationException>(() => client.GetAccessControl());
            Assert.Throws<InvalidOperationException>(() => client.SetAccessControl(new PipeSecurity()));
        }
Example #2
0
        public void SetAccessControl_NamedPipeStream()
        {
            string pipeName = GetUniquePipeName();
            var    server   = new NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
            var    client   = new NamedPipeClientStream(".", pipeName, PipeDirection.In, PipeOptions.Asynchronous);

            Task clientConnect = client.ConnectAsync();

            server.WaitForConnection();
            clientConnect.Wait();

            Assert.NotNull(server.GetAccessControl());
            server.SetAccessControl(new PipeSecurity());
            Assert.NotNull(client.GetAccessControl());
            client.SetAccessControl(new PipeSecurity());
        }
Example #3
0
 /// <summary>
 /// Check to ensure that the named pipe server we connected to is owned by the same
 /// user.
 /// </summary>
 /// <remarks>
 /// The type is embedded in assemblies that need to run cross platform.  While this particular
 /// code will never be hit when running on non-Windows platforms it does need to work when
 /// on Windows.  To facilitate that we use reflection to make the check here to enable it to
 /// compile into our cross plat assemblies.
 /// </remarks>
 private static bool CheckPipeConnectionOwnership(NamedPipeClientStream pipeStream)
 {
     try
     {
         var currentIdentity    = WindowsIdentity.GetCurrent();
         var currentOwner       = currentIdentity.Owner;
         var remotePipeSecurity = pipeStream.GetAccessControl();
         var remoteOwner        = remotePipeSecurity.GetOwner(typeof(SecurityIdentifier));
         return(currentOwner.Equals(remoteOwner));
     }
     catch (Exception ex)
     {
         Log("Exception checking pipe connection: {0}", ex.Message);
         return(false);
     }
 }
Example #4
0
        //  This code needs to be in a separate method so that we don't try (and fail) to load the Windows-only APIs when JIT-ing the code
        //  on non-Windows operating systems
        private void ValidateRemotePipeSecurityOnWindows(NamedPipeClientStream nodeStream)
        {
            SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner;

#if FEATURE_PIPE_SECURITY
            PipeSecurity remoteSecurity = nodeStream.GetAccessControl();
#else
            var remoteSecurity = new PipeSecurity(nodeStream.SafePipeHandle, System.Security.AccessControl.AccessControlSections.Access |
                                                  System.Security.AccessControl.AccessControlSections.Owner | System.Security.AccessControl.AccessControlSections.Group);
#endif
            IdentityReference remoteOwner = remoteSecurity.GetOwner(typeof(SecurityIdentifier));
            if (remoteOwner != identifier)
            {
                CommunicationsUtilities.Trace("The remote pipe owner {0} does not match {1}", remoteOwner.Value, identifier.Value);
                throw new UnauthorizedAccessException();
            }
        }
Example #5
0
        public void SetAccessControl_NamedPipeStream_ClientHandleClosed()
        {
            string pipeName = GetUniquePipeName();
            var    server   = new NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
            var    client   = new NamedPipeClientStream(".", pipeName, PipeDirection.In, PipeOptions.Asynchronous);

            Task clientConnect = client.ConnectAsync();

            server.WaitForConnection();
            clientConnect.Wait();

            // ServerStream.State = Broken, ClientStream.State = Closed
            client.SafePipeHandle.Close();
            Assert.Throws <IOException>(() => server.Write(new byte[] { 0 }, 0, 1));
            Assert.NotNull(server.GetAccessControl());
            server.SetAccessControl(new PipeSecurity());
            Assert.Throws <ObjectDisposedException>(() => client.GetAccessControl());
            Assert.Throws <ObjectDisposedException>(() => client.SetAccessControl(new PipeSecurity()));
        }
Example #6
0
        public void SetAccessControl_NamedPipeStream_ServerDisconnected()
        {
            string pipeName = GetUniquePipeName();
            var    server   = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
            var    client   = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous);

            Task clientConnect = client.ConnectAsync();

            server.WaitForConnection();
            clientConnect.Wait();

            // ServerStream.State = Disconnected, ClientStream.State = Broken
            server.Disconnect();
            Assert.Throws <IOException>(() => client.Write(new byte[] { 0 }, 0, 1));
            Assert.NotNull(server.GetAccessControl());
            server.SetAccessControl(new PipeSecurity());
            Assert.Throws <InvalidOperationException>(() => client.GetAccessControl());
            Assert.Throws <IOException>(() => client.SetAccessControl(new PipeSecurity()));
        }
        public void GetAccessControl_NamedPipeServerStream_Broken()
        {
            string pipeName = GetUniquePipeName();
            var server = new NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
            var client = new NamedPipeClientStream(".", pipeName, PipeDirection.In, PipeOptions.Asynchronous);

            Task clientConnect = client.ConnectAsync();
            server.WaitForConnection();
            clientConnect.Wait();

            Assert.NotNull(server.GetAccessControl());
            server.SetAccessControl(new PipeSecurity());
            Assert.NotNull(client.GetAccessControl());
            client.SetAccessControl(new PipeSecurity());

            client.Dispose();
            Assert.Throws<IOException>(() => server.Write(new byte[] { 0 }, 0, 1)); // Sets the servers PipeState to Broken
            server.SetAccessControl(new PipeSecurity());
        }
Example #8
0
        /// <summary>
        /// Connect to the given process id and return a pipe.
        /// Throws on cancellation.
        /// </summary>
        /// <param name="processId">Process id to try to connect to.</param>
        /// <param name="timeoutMs">Timeout to allow in connecting to process.</param>
        /// <param name="cancellationToken">Cancellation token to cancel connection to server.</param>
        /// <returns>
        /// An open <see cref="NamedPipeClientStream"/> to the server process or null on failure.
        /// </returns>
        private static NamedPipeClientStream TryConnectToProcess(
            int processId,
            int timeoutMs,
            CancellationToken cancellationToken)
        {
            NamedPipeClientStream pipeStream;

            try
            {
                // Machine-local named pipes are named "\\.\pipe\<pipename>".
                // We use the pipe name followed by the process id.
                // The NamedPipeClientStream class handles the "\\.\pipe\" part for us.
                string pipeName = PipeName + processId.ToString();
                Log("Attempt to open named pipe '{0}'", pipeName);

                pipeStream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
                cancellationToken.ThrowIfCancellationRequested();

                Log("Attempt to connect named pipe '{0}'", pipeName);
                pipeStream.Connect(timeoutMs);
                Log("Named pipe '{0}' connected", pipeName);

                cancellationToken.ThrowIfCancellationRequested();

                // Verify that we own the pipe.
                SecurityIdentifier currentIdentity = WindowsIdentity.GetCurrent().Owner;
                PipeSecurity       remoteSecurity  = pipeStream.GetAccessControl();
                IdentityReference  remoteOwner     = remoteSecurity.GetOwner(typeof(SecurityIdentifier));
                if (remoteOwner != currentIdentity)
                {
                    Log("Owner of named pipe is incorrect");
                    return(null);
                }

                return(pipeStream);
            }
            catch (Exception e) when(!(e is TaskCanceledException))
            {
                LogException(e, "Exception while connecting to process");
                return(null);
            }
        }
        /// <summary>
        /// Attempts to connect to the specified process.
        /// </summary>
        private NamedPipeClientStream TryConnectToProcess(int nodeProcessId, int timeout, long hostHandshake, long clientHandshake)
        {
            // Try and connect to the process.
            string pipeName = "MSBuild" + nodeProcessId;

            NamedPipeClientStream nodeStream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous);

            CommunicationsUtilities.Trace("Attempting connect to PID {0} with pipe {1} with timeout {2} ms", nodeProcessId, pipeName, timeout);

            try
            {
                nodeStream.Connect(timeout);

                // Verify that the owner of the pipe is us.  This prevents a security hole where a remote node has
                // been faked up with ACLs that would let us attach to it.  It could then issue fake build requests back to
                // us, potentially causing us to execute builds that do harmful or unexpected things.  The pipe owner can
                // only be set to the user's own SID by a normal, unprivileged process.  The conditions where a faked up
                // remote node could set the owner to something else would also let it change owners on other objects, so
                // this would be a security flaw upstream of us.
                SecurityIdentifier identifier     = WindowsIdentity.GetCurrent().Owner;
                PipeSecurity       remoteSecurity = nodeStream.GetAccessControl();
                IdentityReference  remoteOwner    = remoteSecurity.GetOwner(typeof(SecurityIdentifier));
                if (remoteOwner != identifier)
                {
                    CommunicationsUtilities.Trace("The remote pipe owner {0} does not match {1}", remoteOwner.Value, identifier.Value);
                    throw new UnauthorizedAccessException();
                }

                CommunicationsUtilities.Trace("Writing handshake to pipe {0}", pipeName);
#if true
                nodeStream.WriteLongForHandshake(hostHandshake);
#else
                // When the 4th and subsequent node start up, we see this taking a long period of time (0.5s or greater.)  This is strictly for debugging purposes.
                DateTime writeStart = DateTime.UtcNow;
                nodeStream.WriteLong(HostHandshake);
                DateTime writeEnd = DateTime.UtcNow;
                Console.WriteLine("Node ProcessId {0} WriteLong {1}", nodeProcessId, (writeEnd - writeStart).TotalSeconds);
#endif
                CommunicationsUtilities.Trace("Reading handshake from pipe {0}", pipeName);
                long handshake = nodeStream.ReadLongForHandshake();

                if (handshake != clientHandshake)
                {
                    CommunicationsUtilities.Trace("Handshake failed. Received {0} from client not {1}. Probably the client is a different MSBuild build.", handshake, clientHandshake);
                    throw new InvalidOperationException();
                }

                // We got a connection.
                CommunicationsUtilities.Trace("Successfully connected to pipe {0}...!", pipeName);
                return(nodeStream);
            }
            catch (Exception e)
            {
                if (ExceptionHandling.IsCriticalException(e))
                {
                    throw;
                }

                // Can be:
                // UnauthorizedAccessException -- Couldn't connect, might not be a node.
                // IOException -- Couldn't connect, already in use.
                // TimeoutException -- Couldn't connect, might not be a node.
                // InvalidOperationException – Couldn’t connect, probably a different build
                CommunicationsUtilities.Trace("Failed to connect to pipe {0}. {1}", pipeName, e.Message.TrimEnd());

                // If we don't close any stream, we might hang up the child
                if (nodeStream != null)
                {
                    nodeStream.Close();
                }
            }

            return(null);
        }