public async Task TestDynamicallyLoadedLibrariesReportedOnLinux() { if (!OperatingSystemHelper.IsLinuxOS) { return; } var proc = ToProcess(Operation.Echo("hi")); var info = ToProcessInfo(proc, nameof(TestDynamicallyLoadedLibrariesReportedOnLinux)); info.FileAccessManifest.ReportFileAccesses = true; info.FileAccessManifest.FailUnexpectedFileAccesses = false; using ISandboxedProcess process = await StartProcessAsync(info); var result = await process.GetResultAsync(); XAssert.AreEqual(0, result.ExitCode); var accesses = result.FileAccesses .Select(fa => fa.GetPath(Context.PathTable)) .Where(p => p.EndsWith(".so")) .Select(p => Path.GetFileName(p)) .Select(n => n.Contains('-') ? n.Substring(0, n.IndexOf('-')) + ".so" : n) .Distinct() .ToHashSet(); XAssert.Contains(accesses, new[] { "libhostfxr.so", "libhostpolicy.so", "libclrjit.so", "libcoreclr.so" }); XAssert.ContainsNot(accesses, new[] { "libDetours.so" }); }
protected static async Task <SandboxedProcessResult> RunProcess(SandboxedProcessInfo info) { using (ISandboxedProcess process = await StartProcessAsync(info)) { return(await process.GetResultAsync()); } }
private async Task <SandboxedProcessResult> RunWithPluginAsync( bool shimAllProcesses, bool shouldBeShimmed, bool shouldFindMatch, string[] processMatches, StringBuilder stdOutSb, StringBuilder stdErrSb, string shimmedText = null) { var context = BuildXLContext.CreateInstanceForTesting(); var shimProgramPath = GetShimProgramPath(context); var pluginDlls = GetPluginDlls(context); string executable = CmdHelper.CmdX64; var fam = CreateCommonFileAccessManifest(context.PathTable); fam.SubstituteProcessExecutionInfo = new SubstituteProcessExecutionInfo( shimProgramPath, shimAllProcesses: shimAllProcesses, processMatches: processMatches.Select(pm => new ShimProcessMatch(PathAtom.Create(context.StringTable, pm), PathAtom.Invalid)).ToArray()) { SubstituteProcessExecutionPluginDll32Path = pluginDlls.x86Dll, SubstituteProcessExecutionPluginDll64Path = pluginDlls.x64Dll }; string childOutput = GetChildOutputForPluginTest(shouldBeShimmed, shimmedText); string childArgs = $"{executable} /D /C echo {childOutput}"; string args = $"/D /C echo Top-level cmd. Running child process && {childArgs}"; SandboxedProcessInfo sandboxedProcessInfo = CreateCommonSandboxedProcessInfo( context, executable, args, fam, stdOutSb, stdErrSb); ISandboxedProcess sandboxedProcess = await SandboxedProcessFactory.StartAsync(sandboxedProcessInfo, forceSandboxing : true) .ConfigureAwait(false); SandboxedProcessResult result = await sandboxedProcess.GetResultAsync().ConfigureAwait(false); if (shouldFindMatch || processMatches.Length == 0) { // Plugin should be called when a match is found or no process match is specified. // When plugin is entered, expect to see "Entering CommandMatches" text in the log. // CODESYNC: Public\Src\Engine\UnitTests\Processes.TestPrograms\SubstituteProcessExecutionPlugin\dllmain.cpp AssertLogContains(true, "Entering CommandMatches"); } return(result); }
private async Task <ISandboxedProcess> StartProcessAsync(SandboxedProcessInfo info) { ISandboxedProcess process = null; bool shouldRelaunchProcess = true; int processRelaunchCount = 0; while (shouldRelaunchProcess) { try { shouldRelaunchProcess = false; process = await SandboxedProcessFactory.StartAsync(info, forceSandboxing : false); } catch (BuildXLException ex) { if (ex.LogEventErrorCode == NativeIOConstants.ErrorFileNotFound) { m_logger.LogError($"Failed to start process: '{info.FileName}' not found"); return(null); } else if (ex.LogEventErrorCode == NativeIOConstants.ErrorPartialCopy && (processRelaunchCount < ProcessRelauchCountMax)) { ++processRelaunchCount; shouldRelaunchProcess = true; m_logger.LogInfo($"Retry to start process for {processRelaunchCount} time(s) due to the following error: {ex.LogEventErrorCode}"); // Ensure that process terminates before relaunching it. if (process != null) { try { await process.GetResultAsync(); } finally { process.Dispose(); } } } else { m_logger.LogError($"Failed to start process '{info.FileName}': {ex.LogEventMessage} ({ex.LogEventErrorCode})"); return(null); } } } return(process); }
public async Task CmdWithTestShim_ShimNothingRunsChildProcessWithoutShimAsync(bool shimAllProcess, bool filterMatch) { var context = BuildXLContext.CreateInstanceForTesting(); var shimProgramPath = GetShimProgramPath(context); string executable = CmdHelper.CmdX64; string processMatch = filterMatch ? null : "foo.exe"; // Filter should never match foo.exe. var fam = CreateCommonFileAccessManifest(context.PathTable); fam.SubstituteProcessExecutionInfo = new SubstituteProcessExecutionInfo( shimProgramPath, shimAllProcesses: shimAllProcess, processMatches: processMatch == null ? new ShimProcessMatch[0] : new[] { new ShimProcessMatch(PathAtom.Create(context.StringTable, processMatch), PathAtom.Invalid) }); string predicate = shimAllProcess ? string.Empty : "not "; string childOutput = $"Child cmd that should {predicate}be shimmed"; string childArgs = $"{executable} /D /C @echo {childOutput}"; string args = "/D /C echo Top-level cmd. Running child process && " + childArgs; var stdOutSb = new StringBuilder(128); var stdErrSb = new StringBuilder(); SandboxedProcessInfo sandboxedProcessInfo = CreateCommonSandboxedProcessInfo( context, executable, args, fam, stdOutSb, stdErrSb); ISandboxedProcess sandboxedProcess = await SandboxedProcessFactory.StartAsync(sandboxedProcessInfo, forceSandboxing : true) .ConfigureAwait(false); SandboxedProcessResult result = await sandboxedProcess.GetResultAsync().ConfigureAwait(false); string stdOut = stdOutSb.ToString(); string stdErr = stdErrSb.ToString(); m_output.WriteLine($"stdout: {stdOut}"); m_output.WriteLine($"stderr: {stdErr}"); AssertSuccess(result, stdErr); AssertShimmedIf(stdOut, shimAllProcess); XAssert.Contains(stdOut, childOutput); }
[InlineData("\"\"", "\"\"")] // Force case where child process is executed with double quotes around it public async Task CmdWithSingleTokenChildProcessNoArgsAsync(string preCommand, string postCommand) { var context = BuildXLContext.CreateInstanceForTesting(); var shimProgramPath = GetShimProgramPath(context); string executable = CmdHelper.CmdX64; string childExecutable = executable; string quotedExecutable = '"' + executable + '"'; childExecutable = quotedExecutable; var fam = CreateCommonFileAccessManifest(context.PathTable); // Use 'doskey' (alias manager) built into Windows. string args = $"/D /C {preCommand}doskey.exe{postCommand}"; fam.SubstituteProcessExecutionInfo = new SubstituteProcessExecutionInfo( shimProgramPath, shimAllProcesses: false, processMatches: new[] { new ShimProcessMatch(PathAtom.Create(context.StringTable, "cmd.exe"), PathAtom.Invalid) }); var stdOutSb = new StringBuilder(128); var stdErrSb = new StringBuilder(); SandboxedProcessInfo sandboxedProcessInfo = CreateCommonSandboxedProcessInfo( context, executable, args, fam, stdOutSb, stdErrSb); ISandboxedProcess sandboxedProcess = await SandboxedProcessFactory.StartAsync(sandboxedProcessInfo, forceSandboxing : true) .ConfigureAwait(false); SandboxedProcessResult result = await sandboxedProcess.GetResultAsync().ConfigureAwait(false); string stdOut = stdOutSb.ToString(); string stdErr = stdErrSb.ToString(); m_output.WriteLine($"stdout: {stdOut}"); m_output.WriteLine($"stderr: {stdErr}"); AssertSuccess(result, stdErr); }
public async Task CmdWithStartQuoteOnlyFailsToRunFullCommandLineAsync() { var context = BuildXLContext.CreateInstanceForTesting(); var shimProgramPath = GetShimProgramPath(context); var fam = CreateCommonFileAccessManifest(context.PathTable); fam.SubstituteProcessExecutionInfo = new SubstituteProcessExecutionInfo( shimProgramPath, shimAllProcesses: true, processMatches: new ShimProcessMatch[0]); string executable = CmdHelper.CmdX64; string childExecutable = '"' + executable; // Only an open quote string childOutput = "Child cmd that should be shimmed"; string childArgs = $"{childExecutable} /D /C @echo {childOutput}"; string args = "/D /C echo Top-level cmd. Running child process && " + childArgs; var stdOutSb = new StringBuilder(128); var stdErrSb = new StringBuilder(); SandboxedProcessInfo sandboxedProcessInfo = CreateCommonSandboxedProcessInfo( context, executable, args, fam, stdOutSb, stdErrSb); ISandboxedProcess sandboxedProcess = await SandboxedProcessFactory.StartAsync(sandboxedProcessInfo, forceSandboxing : true) .ConfigureAwait(false); SandboxedProcessResult result = await sandboxedProcess.GetResultAsync().ConfigureAwait(false); string stdOut = stdOutSb.ToString(); string stdErr = stdErrSb.ToString(); m_output.WriteLine($"stdout: {stdOut}"); m_output.WriteLine($"stderr: {stdErr}"); XAssert.AreEqual(1, result.ExitCode); XAssert.AreEqual("The system cannot find the path specified.\r\n", stdErr); }
/// <summary> /// Entry point for an I/O task which creates a process. /// </summary> /// <remarks> /// This is a separate function and not inlined as an anonymous delegate, as VS seems to have trouble with those when /// measuring code coverage /// </remarks> private static ISandboxedProcess ProcessStart(object state) { Counters.IncrementCounter(SandboxedProcessCounters.SandboxedProcessCount); var stateTuple = (Tuple <SandboxedProcessInfo, bool>)state; SandboxedProcessInfo info = stateTuple.Item1; ISandboxedProcess result = null; try { result = Create(info, forceSandboxing: stateTuple.Item2); result.Start(); // this can take a while; performs I/O } catch { result?.Dispose(); throw; } return(result); }
/// <summary> /// Entry point for an I/O task which creates a process. /// </summary> /// <remarks> /// This is a separate function and not inlined as an anonymous delegate, as VS seems to have trouble with those when /// measuring code coverage /// </remarks> private static ISandboxedProcess ProcessStart(object state) { var stateTuple = (Tuple <SandboxedProcessInfo, bool>)state; SandboxedProcessInfo info = stateTuple.Item1; ISandboxedProcess result = null; try { result = Create(info, forceSandboxing: stateTuple.Item2); result.Start(); // this can take a while; performs I/O } catch { result?.Dispose(); info.ProcessIdListener?.Invoke(-1); throw; } return(result); }
/// <summary> /// Starts process asynchronously. /// </summary> public static Task <ISandboxedProcess> StartAsync( SandboxedProcessInfo info, Func <SandboxedProcessInfo, ExternalSandboxedProcess> externalSandboxedProcessFactory) { return(Task.Factory.StartNew(() => { ISandboxedProcess process = externalSandboxedProcessFactory(info); try { process.Start(); } catch { process?.Dispose(); throw; } return process; })); }
private async Task <(ExitCode, SandboxedProcessResult)> ExecuteAsync(SandboxedProcessInfo info) { try { using (Stream standardInputStream = TryOpenStandardInputStream(info, out bool succeedInOpeningStdIn)) { if (!succeedInOpeningStdIn) { return(ExitCode.FailedSandboxPreparation, null); } using (StreamReader standardInputReader = standardInputStream == null ? null : new StreamReader(standardInputStream, CharUtilities.Utf8NoBomNoThrow)) { info.StandardInputReader = standardInputReader; ISandboxedProcess process = await StartProcessAsync(info); if (process == null) { return(ExitCode.FailedStartProcess, null); } SandboxedProcessResult result = await process.GetResultAsync(); // Patch result. result.WarningCount = m_outputErrorObserver.WarningCount; result.LastMessageCount = process.GetLastMessageCount(); result.DetoursMaxHeapSize = process.GetDetoursMaxHeapSize(); result.MessageCountSemaphoreCreated = info.FileAccessManifest.MessageCountSemaphore != null; return(ExitCode.Success, result); } } } finally { info.FileAccessManifest.UnsetMessageCountSemaphore(); } }
public async Task CmdWithStartQuoteOnlyFailsToRunFullCommandLine() { var context = BuildXLContext.CreateInstanceForTesting(); string currentCodeFolder = Path.GetDirectoryName(AssemblyHelper.GetAssemblyLocation(Assembly.GetExecutingAssembly())); Contract.Assume(currentCodeFolder != null); string shimProgram = Path.Combine(currentCodeFolder, "TestSubstituteProcessExecutionShim.exe"); Assert.True(File.Exists(shimProgram), $"Shim test program not found at {shimProgram}"); var shimProgramPath = AbsolutePath.Create(context.PathTable, shimProgram); var fam = new FileAccessManifest(context.PathTable) { FailUnexpectedFileAccesses = false, IgnoreCodeCoverage = false, ReportFileAccesses = false, ReportUnexpectedFileAccesses = false, MonitorChildProcesses = false, SubstituteProcessExecutionInfo = new SubstituteProcessExecutionInfo( shimProgramPath, shimAllProcesses: true, new ShimProcessMatch[0]) }; Guid sessionId = Guid.NewGuid(); string sessionIdStr = sessionId.ToString("N"); var loggingContext = new LoggingContext(sessionId, "TestSession", new LoggingContext.SessionInfo(sessionIdStr, "env", sessionId)); string executable = CmdHelper.CmdX64; string childExecutable = '"' + executable; // Only an open quote string childOutput = "Child cmd that should be shimmed"; string childArgs = $"{childExecutable} /D /C @echo {childOutput}"; string args = "/D /C echo Top-level cmd. Running child process && " + childArgs; var stdoutSb = new StringBuilder(128); var stderrSb = new StringBuilder(); var sandboxedProcessInfo = new SandboxedProcessInfo( context.PathTable, new LocalSandboxedFileStorage(), executable, disableConHostSharing: true, loggingContext: loggingContext, fileAccessManifest: fam) { PipDescription = executable, Arguments = args, WorkingDirectory = Environment.CurrentDirectory, StandardOutputEncoding = Encoding.UTF8, StandardOutputObserver = stdoutStr => stdoutSb.AppendLine(stdoutStr), StandardErrorEncoding = Encoding.UTF8, StandardErrorObserver = stderrStr => stderrSb.AppendLine(stderrStr), EnvironmentVariables = BuildParameters.GetFactory().PopulateFromEnvironment(), Timeout = TimeSpan.FromMinutes(1), }; ISandboxedProcess sandboxedProcess = await SandboxedProcessFactory.StartAsync(sandboxedProcessInfo, forceSandboxing : true) .ConfigureAwait(false); SandboxedProcessResult result = await sandboxedProcess.GetResultAsync().ConfigureAwait(false); string stdout = stdoutSb.ToString(); m_output.WriteLine($"stdout: {stdout}"); string stderr = stderrSb.ToString(); m_output.WriteLine($"stderr: {stderr}"); Assert.Equal(1, result.ExitCode); Assert.Equal("The system cannot find the path specified.\r\n", stderr); }
private async Task VerifyReporting(AccessType access, Action <FileAccessManifest> populateManifest, params ExpectedReportEntry[] expectedExplicitReports) { // We need a process which will generate the expected accesses. var testProcess = CreateTestProcess(access, expectedExplicitReports); var pathTable = Context.PathTable; var info = ToProcessInfo(testProcess, "FileAccessExplicitReportingTest"); info.FileAccessManifest.ReportFileAccesses = false; info.FileAccessManifest.ReportUnexpectedFileAccesses = true; info.FileAccessManifest.FailUnexpectedFileAccesses = false; populateManifest(info.FileAccessManifest); using (ISandboxedProcess process = await StartProcessAsync(info)) { SandboxedProcessResult result = await process.GetResultAsync(); XAssert.AreEqual(0, result.ExitCode, "\r\ncmd: {0} \r\nStandard out: '{1}' \r\nStandard err: '{2}'.", info.Arguments, await result.StandardOutput.ReadValueAsync(), await result.StandardError.ReadValueAsync()); XAssert.IsNotNull(result.ExplicitlyReportedFileAccesses); Dictionary <AbsolutePath, ExpectedReportEntry> pathsToExpectations = expectedExplicitReports.ToDictionary( e => e.File.Path, e => e); var verifiedPaths = new HashSet <AbsolutePath>(); foreach (var actualReport in result.ExplicitlyReportedFileAccesses) { string actualReportedPathString = actualReport.GetPath(pathTable); XAssert.AreEqual( FileAccessStatus.Allowed, actualReport.Status, "Incorrect status for path " + actualReportedPathString); if (!TryVerifySingleReport(pathTable, actualReport, access, pathsToExpectations, out var actualReportPath)) { if ((actualReport.RequestedAccess & RequestedAccess.Enumerate) != 0) { // To account for 'explicitly reported' enumerations globally, we need to be lenient about unexpected enumerations. // Alternatively instead opt-in to enumeration reports under certain scopes. } else { AbsolutePath actualReportedPath = AbsolutePath.Create(pathTable, actualReportedPathString); XAssert.Fail("No expectations for an explicitly reported path {0}", actualReportedPath.ToString(pathTable)); } } else { verifiedPaths.Add(actualReportPath); } } foreach (var actualReport in result.AllUnexpectedFileAccesses) { XAssert.AreEqual(FileAccessStatus.Denied, actualReport.Status); if (TryVerifySingleReport(pathTable, actualReport, access, pathsToExpectations, out var actualReportPath)) { verifiedPaths.Add(actualReportPath); } // Note that we allow extra unexpected file accesses for the purposes of these tests. } var expectedPathsSet = new HashSet <AbsolutePath>(pathsToExpectations.Keys); var disagreeingPaths = new HashSet <AbsolutePath>(verifiedPaths); disagreeingPaths.SymmetricExceptWith(expectedPathsSet); if (disagreeingPaths.Any()) { var disagreeingReports = "Disagreeing reports:" + string.Join(string.Empty, disagreeingPaths .Select(p => (tag: expectedPathsSet.Contains(p) ? "Missing" : "Unexpected", path: p)) .Select(t => $"{Environment.NewLine} {t.tag} report for path {t.path.ToString(pathTable)}")); var expectedReports = "Expected reports:" + string.Join(string.Empty, pathsToExpectations.Keys .Select(p => $"{Environment.NewLine} {p.ToString(pathTable)}")); var verifiedReports = "Verified reports:" + string.Join(string.Empty, verifiedPaths .Select(p => $"{Environment.NewLine} {p.ToString(pathTable)}")); XAssert.Fail(string.Join(Environment.NewLine, disagreeingReports, expectedReports, verifiedReports)); } } }
private async Task <(ExitCode, SandboxedProcessResult)> ExecuteAsync(SandboxedProcessInfo info) { m_logger.LogInfo($"Start execution: {info.GetCommandLine()}"); FileAccessManifest fam = info.FileAccessManifest; try { if (fam.CheckDetoursMessageCount && !OperatingSystemHelper.IsUnixOS) { string semaphoreName = !string.IsNullOrEmpty(info.DetoursFailureFile) ? info.DetoursFailureFile.Replace('\\', '_') : "Detours_" + info.PipSemiStableHash.ToString("X16", CultureInfo.InvariantCulture) + "-" + Guid.NewGuid().ToString(); int maxRetry = 3; while (!fam.SetMessageCountSemaphore(semaphoreName)) { m_logger.LogInfo($"Semaphore '{semaphoreName}' for counting Detours messages is already opened"); fam.UnsetMessageCountSemaphore(); --maxRetry; if (maxRetry == 0) { break; } semaphoreName += $"_{maxRetry}"; } if (maxRetry == 0) { m_logger.LogError($"Semaphore for counting Detours messages cannot be newly created"); return(ExitCode.FailedSandboxPreparation, null); } } using (Stream standardInputStream = TryOpenStandardInputStream(info, out bool succeedInOpeningStdIn)) { if (!succeedInOpeningStdIn) { return(ExitCode.FailedSandboxPreparation, null); } using (StreamReader standardInputReader = standardInputStream == null ? null : new StreamReader(standardInputStream, CharUtilities.Utf8NoBomNoThrow)) { info.StandardInputReader = standardInputReader; ISandboxedProcess process = await StartProcessAsync(info); if (process == null) { return(ExitCode.FailedStartProcess, null); } SandboxedProcessResult result = await process.GetResultAsync(); // Patch result. result.WarningCount = m_outputErrorObserver.WarningCount; result.LastMessageCount = process.GetLastMessageCount(); result.DetoursMaxHeapSize = process.GetDetoursMaxHeapSize(); result.MessageCountSemaphoreCreated = info.FileAccessManifest.MessageCountSemaphore != null; return(ExitCode.Success, result); } } } finally { fam.UnsetMessageCountSemaphore(); } }
[InlineData(false, "cmd.exe")] // Filter should match child public async Task CmdWithTestShimAsync(bool useQuotesForChildCmdExe, string processMatch) { var context = BuildXLContext.CreateInstanceForTesting(); var shimProgramPath = GetShimProgramPath(context); string executable = CmdHelper.CmdX64; string childExecutable = executable; string quotedExecutable = '"' + executable + '"'; if (useQuotesForChildCmdExe) { childExecutable = quotedExecutable; } var fam = CreateCommonFileAccessManifest(context.PathTable); fam.SubstituteProcessExecutionInfo = new SubstituteProcessExecutionInfo( shimProgramPath, shimAllProcesses: processMatch == null, // When we have a process to match, make the shim list opt-in to ensure a match processMatches: processMatch == null ? new ShimProcessMatch[0] : new[] { new ShimProcessMatch(PathAtom.Create(context.StringTable, processMatch), PathAtom.Invalid) }); string childOutput = "Child cmd that should be shimmed"; string childArgs = $"{childExecutable} /D /C @echo {childOutput}"; // Detours logic should wrap the initial cmd in quotes for easier parsing by shim logic. // However, since we're indirecting through a cmd.exe command line it gets dropped along the way. string childShimArgs = $"/D /C @echo {childOutput}"; string args = $"/D /C echo Top-level cmd. Running child process && {childArgs}"; var stdOutSb = new StringBuilder(128); var stdErrSb = new StringBuilder(); SandboxedProcessInfo sandboxedProcessInfo = CreateCommonSandboxedProcessInfo( context, executable, args, fam, stdOutSb, stdErrSb); ISandboxedProcess sandboxedProcess = await SandboxedProcessFactory.StartAsync(sandboxedProcessInfo, forceSandboxing : true) .ConfigureAwait(false); SandboxedProcessResult result = await sandboxedProcess.GetResultAsync().ConfigureAwait(false); string stdOut = stdOutSb.ToString(); string stdErr = stdErrSb.ToString(); m_output.WriteLine($"stdout: {stdOut}"); m_output.WriteLine($"stderr: {stdErr}"); AssertSuccess(result, stdErr); // The shim is an exe on netframework, dll in a temp dir on netcore, so don't try to match it. AssertShimmed(stdOut); AssertShimArgs(stdOut, childShimArgs); }
[InlineData(true, "foo.exe")] // Filter should not match public async Task CmdWithTestShim_ShimNothingRunsChildProcessWithoutShim(bool shimAllProcesses, string processMatch) { var context = BuildXLContext.CreateInstanceForTesting(); string currentCodeFolder = Path.GetDirectoryName(AssemblyHelper.GetAssemblyLocation(Assembly.GetExecutingAssembly())); Contract.Assume(currentCodeFolder != null); string executable = CmdHelper.CmdX64; string shimProgram = Path.Combine(currentCodeFolder, "TestSubstituteProcessExecutionShim.exe"); Assert.True(File.Exists(shimProgram), $"Shim test program not found at {shimProgram}"); var shimProgramPath = AbsolutePath.Create(context.PathTable, shimProgram); var fam = new FileAccessManifest(context.PathTable) { FailUnexpectedFileAccesses = false, IgnoreCodeCoverage = false, ReportFileAccesses = false, ReportUnexpectedFileAccesses = false, MonitorChildProcesses = false, SubstituteProcessExecutionInfo = new SubstituteProcessExecutionInfo( shimProgramPath, shimAllProcesses: false, processMatch == null ? new ShimProcessMatch[0] : new[] { new ShimProcessMatch(PathAtom.Create(context.StringTable, processMatch), PathAtom.Invalid) }) }; Guid sessionId = Guid.NewGuid(); string sessionIdStr = sessionId.ToString("N"); var loggingContext = new LoggingContext(sessionId, "TestSession", new LoggingContext.SessionInfo(sessionIdStr, "env", sessionId)); string childOutput = "Child cmd that should not be shimmed"; string childArgs = $"{executable} /D /C @echo {childOutput}"; string args = "/D /C echo Top-level cmd. Running child process && " + childArgs; var stdoutSb = new StringBuilder(128); var stderrSb = new StringBuilder(); var sandboxedProcessInfo = new SandboxedProcessInfo( context.PathTable, new LocalSandboxedFileStorage(), executable, disableConHostSharing: true, loggingContext: loggingContext, fileAccessManifest: fam) { PipDescription = executable, Arguments = args, WorkingDirectory = Environment.CurrentDirectory, StandardOutputEncoding = Encoding.UTF8, StandardOutputObserver = stdoutStr => stdoutSb.AppendLine(stdoutStr), StandardErrorEncoding = Encoding.UTF8, StandardErrorObserver = stderrStr => stderrSb.AppendLine(stderrStr), EnvironmentVariables = BuildParameters.GetFactory().PopulateFromEnvironment(), Timeout = TimeSpan.FromMinutes(1), }; ISandboxedProcess sandboxedProcess = await SandboxedProcessFactory.StartAsync(sandboxedProcessInfo, forceSandboxing : true) .ConfigureAwait(false); SandboxedProcessResult result = await sandboxedProcess.GetResultAsync().ConfigureAwait(false); Assert.Equal(0, result.ExitCode); string stdout = stdoutSb.ToString(); m_output.WriteLine($"stdout: {stdout}"); string stderr = stderrSb.ToString(); m_output.WriteLine($"stderr: {stderr}"); Assert.Equal(0, stderr.Length); string shimOutput = "TestShim: Entered with command line"; int indexOfShim = stdout.IndexOf(shimOutput, StringComparison.Ordinal); Assert.True(indexOfShim == -1); int indexOfChild = stdout.LastIndexOf(childOutput, StringComparison.Ordinal); Assert.True(indexOfChild > 0, "Child should have run and written output"); }
[InlineData(false, "cmd.exe")] // Filter should match child public async Task CmdWithTestShim(bool useQuotesForChildCmdExe, string processMatch) { var context = BuildXLContext.CreateInstanceForTesting(); string currentCodeFolder = Path.GetDirectoryName(AssemblyHelper.GetAssemblyLocation(Assembly.GetExecutingAssembly())); Contract.Assume(currentCodeFolder != null); string executable = CmdHelper.CmdX64; string childExecutable = executable; string quotedExecutable = '"' + executable + '"'; if (useQuotesForChildCmdExe) { childExecutable = quotedExecutable; } string shimProgram = Path.Combine(currentCodeFolder, "TestSubstituteProcessExecutionShim.exe"); Assert.True(File.Exists(shimProgram), $"Shim test program not found at {shimProgram}"); var shimProgramPath = AbsolutePath.Create(context.PathTable, shimProgram); var fam = new FileAccessManifest(context.PathTable) { FailUnexpectedFileAccesses = false, IgnoreCodeCoverage = false, ReportFileAccesses = false, ReportUnexpectedFileAccesses = false, MonitorChildProcesses = false, SubstituteProcessExecutionInfo = new SubstituteProcessExecutionInfo( shimProgramPath, shimAllProcesses: processMatch == null, // When we have a process to match, make the shim list opt-in to ensure a match processMatch == null ? new ShimProcessMatch[0] : new[] { new ShimProcessMatch(PathAtom.Create(context.StringTable, processMatch), PathAtom.Invalid) }) }; Guid sessionId = Guid.NewGuid(); string sessionIdStr = sessionId.ToString("N"); var loggingContext = new LoggingContext(sessionId, "TestSession", new LoggingContext.SessionInfo(sessionIdStr, "env", sessionId)); string childOutput = "Child cmd that should be shimmed"; string childArgs = $"{childExecutable} /D /C @echo {childOutput}"; // Detours logic should wrap the initial cmd in quotes for easier parsing by shim logic. // However, since we're indirecting through a cmd.exe command line it gets dropped along the way. string childShimArgs = $"/D /C @echo {childOutput}"; string args = "/D /C echo Top-level cmd. Running child process && " + childArgs; var stdoutSb = new StringBuilder(128); var stderrSb = new StringBuilder(); var sandboxedProcessInfo = new SandboxedProcessInfo( context.PathTable, new LocalSandboxedFileStorage(), executable, disableConHostSharing: true, loggingContext: loggingContext, fileAccessManifest: fam) { PipDescription = executable, Arguments = args, WorkingDirectory = Environment.CurrentDirectory, StandardOutputEncoding = Encoding.UTF8, StandardOutputObserver = stdoutStr => stdoutSb.AppendLine(stdoutStr), StandardErrorEncoding = Encoding.UTF8, StandardErrorObserver = stderrStr => stderrSb.AppendLine(stderrStr), EnvironmentVariables = BuildParameters.GetFactory().PopulateFromEnvironment(), Timeout = TimeSpan.FromMinutes(1), }; ISandboxedProcess sandboxedProcess = await SandboxedProcessFactory.StartAsync(sandboxedProcessInfo, forceSandboxing : true) .ConfigureAwait(false); SandboxedProcessResult result = await sandboxedProcess.GetResultAsync().ConfigureAwait(false); string stdout = stdoutSb.ToString(); m_output.WriteLine($"stdout: {stdout}"); string stderr = stderrSb.ToString(); m_output.WriteLine($"stderr: {stderr}"); Assert.Equal(0, result.ExitCode); Assert.Equal(0, stderr.Length); // The shim is an exe on netframework, dll in a temp dir on netcore, so don't try to match it. const string shimOutput = "TestShim: Entered with command line: "; int indexOfShim = stdout.IndexOf(shimOutput, StringComparison.Ordinal); Assert.True(indexOfShim > 0, shimOutput); m_output.WriteLine($"Expecting shim args: {childShimArgs}"); int indexOfShimArgs = stdout.LastIndexOf(childShimArgs); Assert.True(indexOfShimArgs > indexOfShim); }
private async Task <(ExitCode, SandboxedProcessResult)> ExecuteAsync(SandboxedProcessInfo info) { m_logger.LogInfo($"Start execution: {info.GetCommandLine()}"); FileAccessManifest fam = info.FileAccessManifest; try { if (fam.CheckDetoursMessageCount && !OperatingSystemHelper.IsUnixOS) { string semaphoreName = !string.IsNullOrEmpty(info.DetoursFailureFile) ? info.DetoursFailureFile.Replace('\\', '_') : "Detours_" + info.PipSemiStableHash.ToString("X16", CultureInfo.InvariantCulture) + "-" + Guid.NewGuid().ToString(); int maxRetry = 3; // We check this first due to bug#1873910 when executing in a VM on Cloudbuild. // Creating this Semaphore may fail due to a semaphore with the same name already existing. // The reason for this is currently unknown, however since the name for the failures file is // created in SandboxedProcessPipExecutor.GetDetoursInternalErrorFilePath with a unique guid // along with the pip hash it should be safe to dispose because the name is unique to this pip. if (System.Threading.Semaphore.TryOpenExisting(semaphoreName, out var existingSemaphore)) { m_logger.LogInfo($"Disposing existing semaphore with name '{semaphoreName}'."); // Calling dispose on this will allow us to create a new semaphore with the same name existingSemaphore.Dispose(); } while (!fam.SetMessageCountSemaphore(semaphoreName)) { m_logger.LogInfo($"Semaphore '{semaphoreName}' for counting Detours messages is already opened"); fam.UnsetMessageCountSemaphore(); --maxRetry; if (maxRetry == 0) { break; } semaphoreName += $"_{maxRetry}"; } if (maxRetry == 0) { m_logger.LogError($"Semaphore for counting Detours messages cannot be newly created"); return(ExitCode.FailedSandboxPreparation, null); } } using (Stream standardInputStream = TryOpenStandardInputStream(info, out bool succeedInOpeningStdIn)) { if (!succeedInOpeningStdIn) { return(ExitCode.FailedSandboxPreparation, null); } using (StreamReader standardInputReader = standardInputStream == null ? null : new StreamReader(standardInputStream, CharUtilities.Utf8NoBomNoThrow)) { info.StandardInputReader = standardInputReader; ISandboxedProcess process = await StartProcessAsync(info); if (process == null) { return(ExitCode.FailedStartProcess, null); } SandboxedProcessResult result = await process.GetResultAsync(); // Patch result. result.WarningCount = m_outputErrorObserver.WarningCount; result.LastMessageCount = process.GetLastMessageCount(); result.DetoursMaxHeapSize = process.GetDetoursMaxHeapSize(); result.MessageCountSemaphoreCreated = info.FileAccessManifest.MessageCountSemaphore != null; return(ExitCode.Success, result); } } } finally { fam.UnsetMessageCountSemaphore(); } }