/// <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;
        }
Exemple #2
0
        /// <summary>
        /// Connect to the given process id and return a pipe.
        /// Throws on cancellation.
        /// </summary>
        /// <param name="processId">Proces 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>
        /// <param name="pipeStream">
        /// An open <see cref="NamedPipeClientStream"/> to the server process or null on failure
        /// (including IOException).
        /// </param>
        /// <returns>
        /// </returns>
        private bool TryConnectToProcess(int processId,
       	                                 int timeoutMs,
                                         CancellationToken cancellationToken,
                                         out NamedPipeClientStream pipeStream)
        {
            pipeStream = null;
            cancellationToken.ThrowIfCancellationRequested();

            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 = BuildProtocolConstants.PipeName + processId.ToString();
                CompilerServerLogger.Log("Attempt to open named pipe '{0}'", pipeName);

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

                CompilerServerLogger.Log("Attempt to connect named pipe '{0}'", pipeName);
                pipeStream.Connect(timeoutMs);
                CompilerServerLogger.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)
                {
                    CompilerServerLogger.Log("Owner of named pipe is incorrect");
                    return false;
                }

                return true;
            }
            catch (IOException e)
            {
                CompilerServerLogger.LogException(e, "Opening/connecting named pipe");
                return false;
            }
            catch (TimeoutException e)
            {
                CompilerServerLogger.LogException(e, "Timeout while opening/connecting named pipe");
                return false;
            }
        }
 /// <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;
     }
 }