/// <summary> /// Called right after the process starts executing. /// /// Since we set the process file name to be /bin/sh and its arguments to be empty (<see cref="CreateProcess"/>), /// the process will effectively start in a "suspended" mode, with /bin/sh just waiting for some content to be /// piped to its standard input. Therefore, in this handler we first notify the sandbox of the new process (so that /// it starts tracking it) and then just send the actual process command line to /bin/sh via its standard input. /// </summary> private async Task OnProcessStartedAsync(SandboxedProcessInfo info) { // Generate "Process Created" report because the rest of the system expects to see it before any other file access reports // // IMPORTANT: do this before notifying sandbox kernel extension, because otherwise it can happen that a report // from the extension is received before the "process created" report is handled, causing // a "Should see a process creation before its accesses" assertion exception. ReportProcessCreated(); // Allow read access for /bin/sh // When executed using external tool, the manifest tree has been sealed, and cannot be modified. // We take care of adding this path in the manifest in SandboxedProcessPipExecutor.cs; // see AddUnixSpecificSandcboxedProcessFileAccessPolicies if (!info.FileAccessManifest.IsManifestTreeBlockSealed) { info.FileAccessManifest.AddPath( AbsolutePath.Create(PathTable, Process.StartInfo.FileName), mask: FileAccessPolicy.MaskNothing, values: FileAccessPolicy.AllowReadAlways); } if (OperatingSystemHelper.IsLinuxOS) { m_perfCollector?.Start(); } string processStdinFileName = await FlushStandardInputToFileIfNeededAsync(info); if (!SandboxConnection.NotifyPipStarted(info.LoggingContext, info.FileAccessManifest, this)) { ThrowCouldNotStartProcess("Failed to initialize the sandbox for process observation, make sure BuildXL is setup correctly!"); } try { await FeedStdInAsync(info, processStdinFileName); m_processTreeTimeoutTask = ProcessTreeTimeoutTask(); } catch (IOException e) { // IOException can happen if the process is forcefully killed while we're feeding its std in. // When that happens, instead of crashing, just make sure the process is killed. LogProcessState($"IOException caught while feeding the standard input: {e.ToString()}"); await KillAsync(); } finally { // release the FileAccessManifest memory // NOTE: just by not keeping any references to 'info' should make the FileAccessManifest object // unreachable and thus available for garbage collection. We call Release() here explicitly // just to emphasize the importance of reclaiming this memory. info.FileAccessManifest.Release(); } }
private void KillAllChildProcesses() { var distinctProcessIds = CoalesceProcesses(GetCurrentlyActiveChildProcesses()) .Select(p => p.ProcessId) .ToHashSet(); foreach (int processId in distinctProcessIds) { bool killed = BuildXL.Interop.Unix.Process.ForceQuit(processId); LogProcessState($"KillAllChildProcesses: kill({processId}) = {killed}"); SandboxConnection.NotifyPipProcessTerminated(PipId, processId); } }
/// <inheritdoc /> public override async Task KillAsync() { // In the case that the process gets shut down by either its timeout or e.g. SandboxedProcessPipExecutor // detecting resource usage issues and calling KillAsync(), we flag the process with m_processKilled so we // don't process any more kernel reports that get pushed into report structure asynchronously! long incrementedValue = Interlocked.Increment(ref m_processKilledFlag); // Make sure this is done no more than once. if (incrementedValue == 1) { // surviving child processes may only be set when the process is explicitly killed m_survivingChildProcesses = NullIfEmpty(CoalesceProcesses(GetCurrentlyActiveChildProcesses())); await base.KillAsync(); KillAllChildProcesses(); SandboxConnection.NotifyRootProcessExited(PipId, this); await m_pendingReports.Completion; } }
/// <summary> /// Waits for all child processes to finish within a timeout limit and then termiantes all still running children after that point. /// After all the children have been taken care of, the method waits for pending report processing to finish, then returns the /// collected reports. /// </summary> internal override async Task <SandboxedProcessReports> GetReportsAsync() { SandboxConnection.NotifyRootProcessExited(PipId, this); if (!Killed) { var awaitedTask = await Task.WhenAny(m_pendingReports.Completion, m_processTreeTimeoutTask); if (awaitedTask == m_processTreeTimeoutTask) { LogProcessState("Waiting for reports timed out; any surviving processes will be forcefully killed."); await KillAsync(); } } // in any case must wait for pending reports to complete, because we must not freeze m_reports before that happens await m_pendingReports.Completion; // at this point this pip is done executing (it's only left to construct SandboxedProcessResult, // which is done by the base class) so notify the sandbox connection about it. SandboxConnection.NotifyPipFinished(PipId, this); return(IgnoreReportedAccesses ? null : m_reports); }