/// <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); } }
// 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(); } }
/// <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); }