/// <summary> /// The manifest is configured so all file accesses are allowed but reported, including child processes. /// </summary> /// <remarks> /// Some special folders (Windows, InternetCache and History) are added as known scopes. Everything else will be flagged /// as an 'unexpected' access. However, unexpected accesses are configured so they are not blocked. /// </remarks> private static FileAccessManifest CreateManifestToAllowAllAccesses(PathTable pathTable) { var fileAccessManifest = new FileAccessManifest(pathTable) { FailUnexpectedFileAccesses = false, ReportFileAccesses = true, MonitorChildProcesses = true, }; fileAccessManifest.AddScope( AbsolutePath.Create(pathTable, SpecialFolderUtilities.GetFolderPath(Environment.SpecialFolder.Windows)), FileAccessPolicy.MaskAll, FileAccessPolicy.AllowAll); fileAccessManifest.AddScope( AbsolutePath.Create(pathTable, SpecialFolderUtilities.GetFolderPath(Environment.SpecialFolder.InternetCache)), FileAccessPolicy.MaskAll, FileAccessPolicy.AllowAll); fileAccessManifest.AddScope( AbsolutePath.Create(pathTable, SpecialFolderUtilities.GetFolderPath(Environment.SpecialFolder.History)), FileAccessPolicy.MaskAll, FileAccessPolicy.AllowAll); return(fileAccessManifest); }
/// <summary> /// The manifest is configured so all accesses under the provided collection of directories to block are blocked /// </summary> private FileAccessManifest CreateManifest(AbsolutePath pathToProcess, IEnumerable <AbsolutePath> directoriesToBlock) { var fileAccessManifest = new FileAccessManifest(m_pathTable) { FailUnexpectedFileAccesses = true, ReportFileAccesses = true, MonitorChildProcesses = true, }; // We allow all file accesses at the root level, so by default everything is allowed fileAccessManifest.AddScope(AbsolutePath.Invalid, FileAccessPolicy.MaskNothing, FileAccessPolicy.AllowAll); // We explicitly allow reading from the tool path fileAccessManifest.AddPath(pathToProcess, FileAccessPolicy.MaskAll, FileAccessPolicy.AllowRead); // We block access on all provided directories foreach (var directoryToBlock in directoriesToBlock) { fileAccessManifest.AddScope( directoryToBlock, FileAccessPolicy.MaskAll, FileAccessPolicy.Deny & FileAccessPolicy.ReportAccess); } return(fileAccessManifest); }
/// <summary> /// Creates instance for test /// </summary> public SandboxedProcessInfo( PathTable pathTable, ISandboxedProcessFileStorage fileStorage, string fileName, bool disableConHostSharing, bool testRetries = false, LoggingContext loggingContext = null, IDetoursEventListener detoursEventListener = null, IKextConnection sandboxedKextConnection = null, ContainerConfiguration containerConfiguration = null, FileAccessManifest fileAccessManifest = null) : this( pathTable, fileStorage, fileName, fileAccessManifest ?? new FileAccessManifest(pathTable), disableConHostSharing, containerConfiguration ?? ContainerConfiguration.DisabledIsolation, testRetries, loggingContext, detoursEventListener, sandboxedKextConnection) { Contract.Requires(pathTable != null); Contract.Requires(fileStorage != null); Contract.Requires(fileName != null); }
public void AugmentedAccessHasTheRightManifestPath() { var fam = new FileAccessManifest( Context.PathTable, childProcessesToBreakawayFromSandbox: new[] { TestProcessToolName }) { FailUnexpectedFileAccesses = false, ReportUnexpectedFileAccesses = true, ReportFileAccesses = true }; var basePath = TestBinRootPath.Combine(Context.PathTable, "foo"); var output = CreateOutputFileArtifact(basePath); fam.AddScope(basePath, FileAccessPolicy.MaskNothing, FileAccessPolicy.AllowAll | FileAccessPolicy.ReportAccess); var info = ToProcessInfo( ToProcess( Operation.AugmentedWrite(output)), fileAccessManifest: fam); var result = RunProcess(info).GetAwaiter().GetResult(); XAssert.AreEqual(0, result.ExitCode); var fileAccess = result.ExplicitlyReportedFileAccesses.Single(rfa => rfa.Method == FileAccessStatusMethod.TrustedTool); XAssert.AreEqual(basePath, fileAccess.ManifestPath); }
public void MaskedReportAugmentedAccessIsNotReported() { var fam = new FileAccessManifest( Context.PathTable, childProcessesToBreakawayFromSandbox: new[] { TestProcessToolName }) { FailUnexpectedFileAccesses = false, ReportUnexpectedFileAccesses = true, ReportFileAccesses = true }; var basePath = TestBinRootPath.Combine(Context.PathTable, "foo"); var output1 = CreateOutputFileArtifact(basePath); var output2 = CreateOutputFileArtifact(basePath); // We mask reporting accesses for output1 and enable it for output2 fam.AddScope(output1.Path, ~FileAccessPolicy.ReportAccess, FileAccessPolicy.AllowAll); fam.AddScope(output2.Path, FileAccessPolicy.MaskNothing, FileAccessPolicy.AllowAll | FileAccessPolicy.ReportAccess); var info = ToProcessInfo( ToProcess( Operation.AugmentedWrite(output1), Operation.AugmentedWrite(output2)), fileAccessManifest: fam); var result = RunProcess(info).GetAwaiter().GetResult(); XAssert.AreEqual(0, result.ExitCode); // We should get a single access with output2, since output1 should be ignored var accessPath = result.FileAccesses.Single(rfa => rfa.Method == FileAccessStatusMethod.TrustedTool).ManifestPath; XAssert.AreEqual(output2.Path, accessPath); }
public void BreakawayProcessCanReportAugmentedAccesses() { var fam = new FileAccessManifest( Context.PathTable, childProcessesToBreakawayFromSandbox: new[] { TestProcessToolName }) { FailUnexpectedFileAccesses = false, ReportUnexpectedFileAccesses = true, ReportFileAccesses = true }; var srcFile = CreateSourceFile(); var output = CreateOutputFileArtifact(); fam.AddScope(srcFile, FileAccessPolicy.MaskNothing, FileAccessPolicy.ReportAccess); fam.AddScope(output, FileAccessPolicy.MaskNothing, FileAccessPolicy.ReportAccess); var info = ToProcessInfo( ToProcess( Operation.AugmentedRead(srcFile), Operation.AugmentedWrite(output)), fileAccessManifest: fam); var result = RunProcess(info).GetAwaiter().GetResult(); XAssert.AreEqual(0, result.ExitCode); var observedAccesses = result.FileAccesses.Select(fa => fa.ManifestPath); XAssert.Contains(observedAccesses, srcFile.Path, output.Path); }
/// <summary> /// Creates instance /// </summary> public SandboxedProcessInfo( PathTable pathTable, [CanBeNull] ISandboxedProcessFileStorage fileStorage, string fileName, FileAccessManifest fileAccessManifest, bool disableConHostSharing, ContainerConfiguration containerConfiguration, LoggingContext loggingContext, bool testRetries = false, IDetoursEventListener detoursEventListener = null, ISandboxConnection sandboxConnection = null, SidebandWriter sidebandWriter = null, bool createJobObjectForCurrentProcess = true) { Contract.Requires(pathTable != null); Contract.Requires(fileName != null); PathTable = pathTable; FileAccessManifest = fileAccessManifest; FileStorage = fileStorage; FileName = fileName; DisableConHostSharing = disableConHostSharing; // This should be set for testing purposes only. TestRetries = testRetries; NestedProcessTerminationTimeout = DefaultNestedProcessTerminationTimeout; LoggingContext = loggingContext; DetoursEventListener = detoursEventListener; SandboxConnection = sandboxConnection; ContainerConfiguration = containerConfiguration; SidebandWriter = sidebandWriter; CreateJobObjectForCurrentProcess = createJobObjectForCurrentProcess; }
public SandboxedProcessReports( FileAccessManifest manifest, PathTable pathTable, long pipSemiStableHash, string pipDescription, LoggingContext loggingContext, [CanBeNull] IDetoursEventListener detoursEventListener, [CanBeNull] SidebandWriter sharedOpaqueOutputLogger) { Contract.Requires(manifest != null); Contract.Requires(pathTable != null); Contract.Requires(pipDescription != null); PipSemiStableHash = pipSemiStableHash; PipDescription = pipDescription; m_pathTable = pathTable; FileAccesses = manifest.ReportFileAccesses ? new HashSet <ReportedFileAccess>() : null; FileUnexpectedAccesses = new HashSet <ReportedFileAccess>(); m_manifest = manifest; m_detoursEventListener = detoursEventListener; m_sharedOpaqueOutputLogger = sharedOpaqueOutputLogger; // For tests we need the StaticContext m_loggingContext = loggingContext ?? BuildXL.Utilities.Tracing.Events.StaticContext; }
/// <summary> /// Creates instance for test /// </summary> public SandboxedProcessInfo( PathTable pathTable, [CanBeNull] ISandboxedProcessFileStorage fileStorage, string fileName, bool disableConHostSharing, LoggingContext loggingContext, bool testRetries = false, IDetoursEventListener detoursEventListener = null, ISandboxConnection sandboxConnection = null, ContainerConfiguration containerConfiguration = null, FileAccessManifest fileAccessManifest = null, bool createJobObjectForCurrentProcess = true) : this( pathTable, fileStorage, fileName, fileAccessManifest ?? new FileAccessManifest(pathTable), disableConHostSharing, containerConfiguration ?? ContainerConfiguration.DisabledIsolation, loggingContext, testRetries, detoursEventListener, sandboxConnection, createJobObjectForCurrentProcess : createJobObjectForCurrentProcess) { Contract.Requires(pathTable != null); Contract.Requires(fileName != null); }
/// <summary> /// Adds synthetic accesses for all intermediate directory symlinks for the given access path /// </summary> /// <remarks> /// TODO: This function is only adding accesses for symlinks in the given path, so if those suymlinks point to other symlinks, /// those are not added. So the result is not completely sound, consider doing multi-hop resolution. /// </remarks> public void AddAccessesForIntermediateSymlinks(FileAccessManifest manifest, ReportedFileAccess access, AbsolutePath accessPath, Dictionary <AbsolutePath, CompactSet <ReportedFileAccess> > accessesByPath) { Contract.Requires(accessPath.IsValid); AbsolutePath currentPath = accessPath.GetParent(m_context.PathTable); while (currentPath.IsValid) { // If we the current path is resolved and its resolved path is the same, then we know there are no more symlinks // in the path. Shorcut the search. if (m_resolvedPathCache.TryGetValue(currentPath, out var resolvedPath) && currentPath == resolvedPath) { return; } bool isDirSymlink = IsDirectorySymlinkOrJunctionWithCache(ExpandedAbsolutePath.CreateUnsafe(currentPath, currentPath.ToString(m_context.PathTable))); if (isDirSymlink) { accessesByPath.TryGetValue(currentPath, out CompactSet <ReportedFileAccess> existingAccessesToPath); var generatedProbe = GenerateProbeForPath(manifest, currentPath, access); accessesByPath[currentPath] = existingAccessesToPath.Add(generatedProbe); } currentPath = currentPath.GetParent(m_context.PathTable); } }
/// <summary> /// Creates a common sandboxed process info for tests in this class. /// </summary> private static SandboxedProcessInfo CreateCommonSandboxedProcessInfo( BuildXLContext context, string executable, string arguments, FileAccessManifest fileAccessManifest, StringBuilder stdOutBuilder, StringBuilder stdErrBuilder) { return(new SandboxedProcessInfo( context.PathTable, new LocalSandboxedFileStorage(), executable, disableConHostSharing: true, loggingContext: CreateLoggingContext(), fileAccessManifest: fileAccessManifest) { PipDescription = executable, Arguments = arguments, WorkingDirectory = Environment.CurrentDirectory, StandardOutputEncoding = Encoding.UTF8, StandardOutputObserver = stdOutStr => stdOutBuilder.AppendLine(stdOutStr), StandardErrorEncoding = Encoding.UTF8, StandardErrorObserver = stdErrStr => stdErrBuilder.AppendLine(stdErrStr), EnvironmentVariables = BuildParameters.GetFactory().PopulateFromEnvironment(), Timeout = TimeSpan.FromMinutes(1), }); }
/// <summary> /// Creates instance /// </summary> public SandboxedProcessInfo( PathTable pathTable, ISandboxedProcessFileStorage fileStorage, string fileName, FileAccessManifest fileAccessManifest, bool disableConHostSharing, ContainerConfiguration containerConfiguration, bool testRetries = false, LoggingContext loggingContext = null, IDetoursEventListener detoursEventListener = null, IKextConnection sandboxedKextConnection = null) { Contract.Requires(pathTable != null); Contract.Requires(fileStorage != null); Contract.Requires(fileName != null); PathTable = pathTable; FileAccessManifest = fileAccessManifest; FileStorage = fileStorage; FileName = fileName; DisableConHostSharing = disableConHostSharing; // This should be set for testing purposes only. TestRetries = testRetries; NestedProcessTerminationTimeout = DefaultNestedProcessTerminationTimeout; LoggingContext = loggingContext; DetoursEventListener = detoursEventListener; SandboxedKextConnection = sandboxedKextConnection; ContainerConfiguration = containerConfiguration; }
private FileAccessManifest GenerateFileAccessManifest(AbsolutePath toolDirectory, AbsolutePath outputFile) { // We make no attempt at understanding what the graph generation process is going to do // We just configure the manifest to not fail on unexpected accesses, so they can be collected // later if needed var fileAccessManifest = new FileAccessManifest(m_context.PathTable) { FailUnexpectedFileAccesses = false, ReportFileAccesses = true, MonitorNtCreateFile = true, MonitorZwCreateOpenQueryFile = true, MonitorChildProcesses = true, }; fileAccessManifest.AddScope( AbsolutePath.Create(m_context.PathTable, SpecialFolderUtilities.GetFolderPath(Environment.SpecialFolder.Windows)), FileAccessPolicy.MaskAll, FileAccessPolicy.AllowAllButSymlinkCreation); fileAccessManifest.AddScope( AbsolutePath.Create(m_context.PathTable, SpecialFolderUtilities.GetFolderPath(Environment.SpecialFolder.InternetCache)), FileAccessPolicy.MaskAll, FileAccessPolicy.AllowAllButSymlinkCreation); fileAccessManifest.AddScope( AbsolutePath.Create(m_context.PathTable, SpecialFolderUtilities.GetFolderPath(Environment.SpecialFolder.History)), FileAccessPolicy.MaskAll, FileAccessPolicy.AllowAllButSymlinkCreation); fileAccessManifest.AddScope(toolDirectory, FileAccessPolicy.MaskAll, FileAccessPolicy.AllowReadAlways); fileAccessManifest.AddPath(outputFile, FileAccessPolicy.MaskAll, FileAccessPolicy.AllowWrite); return(fileAccessManifest); }
private bool TryPrepareSandboxedProcess(SandboxedProcessInfo info) { Contract.Requires(info != null); FileAccessManifest fam = info.FileAccessManifest; if (!string.IsNullOrEmpty(fam.InternalDetoursErrorNotificationFile)) { Analysis.IgnoreResult(FileUtilities.TryDeleteFile(fam.InternalDetoursErrorNotificationFile)); } if (fam.CheckDetoursMessageCount && !OperatingSystemHelper.IsUnixOS) { string semaphoreName = fam.InternalDetoursErrorNotificationFile.Replace('\\', '_'); if (!fam.SetMessageCountSemaphore(semaphoreName)) { m_logger.LogError($"Semaphore '{semaphoreName}' for counting Detours messages is already opened"); return(false); } } if (info.GetCommandLine().Length > SandboxedProcessInfo.MaxCommandLineLength) { m_logger.LogError($"Process command line is longer than {SandboxedProcessInfo.MaxCommandLineLength} characters: {info.GetCommandLine().Length}"); return(false); } m_outputErrorObserver = OutputErrorObserver.Create(m_logger, info); info.StandardOutputObserver = m_outputErrorObserver.ObserveStandardOutputForWarning; info.StandardErrorObserver = m_outputErrorObserver.ObserveStandardErrorForWarning; return(true); }
/// <summary> /// Adds synthetic read accesses for all intermediate directory symlinks for the given access path /// </summary> /// <remarks> /// TODO: This function is only adding accesses for symlinks in the given path, so if those suymlinks point to other symlinks, /// those are not added. So the result is not completely sound, consider doing multi-hop resolution. /// </remarks> public void AddReadsForIntermediateSymlinks(FileAccessManifest manifest, ReportedFileAccess access, AbsolutePath accessPath, Dictionary <AbsolutePath, CompactSet <ReportedFileAccess> > accessesByPath) { Contract.Requires(accessPath.IsValid); AbsolutePath currentPath = accessPath.GetParent(m_context.PathTable); while (currentPath.IsValid) { // If we the current path is resolved and its resolved path is the same, then we know there are no more symlinks // in the path. Shorcut the search. if (m_resolvedPathCache.TryGetValue(currentPath, out var resolvedPath) && currentPath == resolvedPath) { return; } bool isDirSymlink; var result = m_symlinkCache.TryGet(currentPath); if (!result.IsFound) { isDirSymlink = FileUtilities.IsDirectorySymlinkOrJunction(currentPath.ToString(m_context.PathTable)); m_symlinkCache.TryAdd(currentPath, isDirSymlink); } else { isDirSymlink = result.Item.Value; } if (isDirSymlink) { accessesByPath.TryGetValue(currentPath, out CompactSet <ReportedFileAccess> existingAccessesToPath); accessesByPath[currentPath] = existingAccessesToPath.Add(GenerateReadAccessForPath(manifest, currentPath, access)); } currentPath = currentPath.GetParent(m_context.PathTable); } }
private void AddBlockedPath(FileAccessManifest fam, SandboxOptions option, AbsolutePath path) { if (option.debug) { Console.Error.WriteLine($"na: {path.ToString(m_pathTable)}"); } fam.AddScope(path, FileAccessPolicy.MaskAll, FileAccessPolicy.Deny); }
public void ChildProcessCanBreakawayWhenConfigured(bool letInfiniteWaiterSurvive) { // We use InfiniteWaiter (a process that waits forever) as a long-living process that we can actually check it can // escape the job object var fam = new FileAccessManifest( Context.PathTable, childProcessesToBreakawayFromSandbox: letInfiniteWaiterSurvive ? new[] { InfiniteWaiterToolName } : null) { FailUnexpectedFileAccesses = false }; // We instruct the regular test process to spawn InfiniteWaiter as a child var info = ToProcessInfo( ToProcess( Operation.SpawnExe( Context.PathTable, CreateFileArtifactWithName(InfiniteWaiterToolName, TestDeploymentDir))), fileAccessManifest: fam); // Let's shorten the default time to wait for nested processes, since we are spawning // a process that never ends and we don't want this test to wait for that long info.NestedProcessTerminationTimeout = TimeSpan.FromMilliseconds(10); var result = RunProcess(info).GetAwaiter().GetResult(); XAssert.AreEqual(0, result.ExitCode); if (!letInfiniteWaiterSurvive) { // If we didn't let infinite waiter escape, we should have killed it when the job object was finalized XAssert.IsTrue(result.Killed); XAssert.Contains( result.SurvivingChildProcesses.Select(p => p?.Path).Where(p => p != null).Select(p => System.IO.Path.GetFileName(p).ToUpperInvariant()), InfiniteWaiterToolName.ToUpperInvariant()); } else { // If we did let it escape, then nothing should have been killed (nor tried to survive and later killed, from the job object point of view) XAssert.IsFalse(result.Killed); if (result.SurvivingChildProcesses != null) { var survivors = string.Join( ", ", result.SurvivingChildProcesses.Select(p => p?.Path != null ? System.IO.Path.GetFileName(p.Path) : "<unknown>")); XAssert.Fail($"Unexpected {result.SurvivingChildProcesses.Count()} surviving child processes: {survivors}"); } // Let's retrieve the child process and confirm it survived var infiniteWaiterInfo = RetrieveChildProcessesCreatedBySpawnExe(result).Single(); // The fact that this does not throw confirms survival var dummyWaiter = Process.GetProcessById(infiniteWaiterInfo.pid); // Just being protective, let's make sure we are talking about the same process XAssert.AreEqual(infiniteWaiterInfo.processName, dummyWaiter.ProcessName); // Now let's kill the surviving process, since we don't want it to linger around unnecessarily dummyWaiter.Kill(); } }
/// <inheritdoc /> public bool NotifyPipStarted(LoggingContext loggingContext, FileAccessManifest fam, SandboxedProcessUnix process) { Contract.Requires(process.Started); Contract.Requires(process.PipId != 0); string rootDir = process.RootJail ?? Path.GetTempPath(); string fifoPath = Path.Combine(rootDir, $"bxl_Pip{process.PipSemiStableHash:X}.{process.ProcessId}.fifo"); string famPath = Path.ChangeExtension(fifoPath, ".fam"); string debugLogPath = null; if (IsInTestMode) { debugLogPath = process.ToPathInsideRootJail(Path.ChangeExtension(fifoPath, ".log")); fam.AddPath(toAbsPath(debugLogPath), mask: FileAccessPolicy.MaskAll, values: FileAccessPolicy.AllowAll); } // serialize FAM using (var wrapper = Pools.MemoryStreamPool.GetInstance()) { var debugFlags = true; ArraySegment <byte> manifestBytes = fam.GetPayloadBytes( loggingContext, new FileAccessSetup { DllNameX64 = string.Empty, DllNameX86 = string.Empty, ReportPath = process.ToPathInsideRootJail(fifoPath) }, wrapper.Instance, timeoutMins: 10, // don't care debugFlagsMatch: ref debugFlags); Contract.Assert(manifestBytes.Offset == 0); File.WriteAllBytes(famPath, manifestBytes.ToArray()); } process.LogDebug($"Saved FAM to '{famPath}'"); // create a FIFO (named pipe) if (IO.MkFifo(fifoPath, IO.FilePermissions.S_IRWXU) != 0) { m_failureCallback?.Invoke(1, $"Creating FIFO {fifoPath} failed"); return(false); } process.LogDebug($"Created FIFO at '{fifoPath}'"); // create and save info for this pip var info = new Info(m_failureCallback, process, fifoPath, famPath, debugLogPath); if (!m_pipProcesses.TryAdd(process.PipId, info)) { throw new BuildXLException($"Process with PidId {process.PipId} already exists"); } info.Start(); return(true); AbsolutePath toAbsPath(string path) => AbsolutePath.Create(process.PathTable, path); }
/// <remarks> /// Sets <see cref="FileAccessManifest.FailUnexpectedFileAccesses"/> to false if not explicitly set /// </remarks> protected SandboxedProcessInfo ToProcessInfo( Process process, string pipDescription = null, FileAccessManifest fileAccessManifest = null, IDetoursEventListener detoursListener = null, bool disableConHostSharing = false, Dictionary <string, string> overrideEnvVars = null, ISandboxConnection sandboxConnection = null) { var envVars = Override( BuildParameters.GetFactory().PopulateFromEnvironment().ToDictionary(), overrideEnvVars); var methodName = DiscoverCurrentlyExecutingXunitTestMethodFQN(); pipDescription = pipDescription != null ? methodName + " - " + pipDescription : methodName; var info = new SandboxedProcessInfo( Context.PathTable, this, process.Executable.Path.ToString(Context.PathTable), detoursEventListener: detoursListener, sandboxConnection: sandboxConnection ?? GetSandboxConnection(), disableConHostSharing: disableConHostSharing, fileAccessManifest: fileAccessManifest, loggingContext: LoggingContext) { PipSemiStableHash = 0x1234, PipDescription = pipDescription, WorkingDirectory = TemporaryDirectory, Arguments = process.Arguments.ToString(Context.PathTable), Timeout = TimeSpan.FromMinutes(15), EnvironmentVariables = BuildParameters.GetFactory().PopulateFromDictionary(envVars) }; if (fileAccessManifest == null) { info.FileAccessManifest.FailUnexpectedFileAccesses = false; } foreach (var path in process.UntrackedPaths) { info.FileAccessManifest.AddPath(path, values: FileAccessPolicy.AllowAll, mask: FileAccessPolicy.MaskNothing); } foreach (var dir in process.UntrackedScopes) { info.FileAccessManifest.AddScope(dir, FileAccessPolicy.MaskNothing, FileAccessPolicy.AllowAll); } info.FileAccessManifest.PipId = GetNextPipId(); return(info); }
public void MaskedReportAugmentedAccessIsNotReported(bool reportFileAccesses) { var fam = new FileAccessManifest( Context.PathTable, childProcessesToBreakawayFromSandbox: new[] { TestProcessToolName }) { FailUnexpectedFileAccesses = false, ReportUnexpectedFileAccesses = true, ReportFileAccesses = reportFileAccesses }; var basePath = TestBinRootPath.Combine(Context.PathTable, "foo"); var output1 = CreateOutputFileArtifact(basePath); var output2 = CreateOutputFileArtifact(basePath); // We mask reporting accesses for output1 and enable it for output2 fam.AddScope(output1.Path, ~FileAccessPolicy.ReportAccess, FileAccessPolicy.AllowAll); fam.AddScope(output2.Path, FileAccessPolicy.MaskNothing, FileAccessPolicy.AllowAll | FileAccessPolicy.ReportAccess); var collector = new FileAccessCollector(Context.PathTable); var info = ToProcessInfo( ToProcess( Operation.AugmentedWrite(output1), Operation.AugmentedWrite(output2)), fileAccessManifest: fam, detoursListener: collector); var result = RunProcess(info).GetAwaiter().GetResult(); XAssert.AreEqual(0, result.ExitCode); // We should get a single explicit access with output2, since output1 shouldn't be reported var accessPath = result.ExplicitlyReportedFileAccesses.Single(rfa => rfa.Method == FileAccessStatusMethod.TrustedTool).ManifestPath; XAssert.AreEqual(output2.Path, accessPath); // We should get both accesses as part of the (optional) FileAccess on request if (reportFileAccesses) { var allTrustedAcceses = result.FileAccesses.Where(rfa => rfa.Method == FileAccessStatusMethod.TrustedTool).Select(rfa => rfa.ManifestPath); XAssert.Contains(allTrustedAcceses, output1.Path, output2.Path); } else { // Make sure the access related to output1 is not actually reported, and the only one the listener got is output2 XAssert.Contains(collector.FileAccessPaths, output2.Path); XAssert.ContainsNot(collector.FileAccessPaths, output1.Path); } }
/// <summary> /// Given an access whose path may contain intermediate symlinks, returns an equivalent resolved access where all the symlinks /// are resolved. /// </summary> /// <remarks> /// If the symlink is the final fragment of the path, this function does not consider it to need resolution. /// <paramref name="accessPath"/> should correspond to the path of <paramref name="access"/>. It is passed explicitly to avoid /// unnecesary conversions between strings and absolute paths. /// </remarks> /// <returns>Whether the given access needed resolution</returns> public bool ResolveDirectorySymlinks(FileAccessManifest manifest, ReportedFileAccess access, AbsolutePath accessPath, out ReportedFileAccess resolvedAccess, out AbsolutePath resolvedPath) { Contract.Requires(accessPath.IsValid); resolvedPath = ResolvePathWithCache( accessPath, access.Path, access.Operation, access.FlagsAndAttributes, out string resolvedPathAsString, out bool isDirectoryReparsePoint); return(ResolveAccess(manifest, access, accessPath, resolvedPath, resolvedPathAsString, isDirectoryReparsePoint, out resolvedAccess)); }
/// <summary> /// The manifest is configured so the sandbox monitors all child processes and collect process data. /// </summary> private static FileAccessManifest CreateManifestToLogProcessData(PathTable pathTable) { var fileAccessManifest = new FileAccessManifest(pathTable) { // We don't want to block any accesses FailUnexpectedFileAccesses = false, // Monitor children processes spawned MonitorChildProcesses = true, // Optional data about the processes LogProcessData = true }; return(fileAccessManifest); }
/// <summary> /// The manifest is configured so the sandbox monitors all child processes and collect process data. /// </summary> private static FileAccessManifest CreateManifestToLogProcessData(PathTable pathTable) { var fileAccessManifest = new FileAccessManifest(pathTable) { // We don't want to block any accesses FailUnexpectedFileAccesses = false, // We are particularly interested in monitoring children, since we are after the process tree MonitorChildProcesses = true, // Let's turn on process data collection, so we can report a richer tree LogProcessData = true }; return(fileAccessManifest); }
/// <nodoc /> public void Serialize(Stream stream) { using (var writer = new BuildXLWriter(false, stream, true, true)) { writer.WriteNullableString(m_arguments); writer.WriteNullableString(m_commandLine); writer.Write(DisableConHostSharing); writer.WriteNullableString(FileName); writer.Write(StandardInputEncoding, (w, v) => w.Write(v)); writer.Write(StandardOutputEncoding, (w, v) => w.Write(v)); writer.Write(StandardErrorEncoding, (w, v) => w.Write(v)); writer.WriteNullableString(WorkingDirectory); writer.Write( EnvironmentVariables, (w, v) => w.WriteReadOnlyList( v.ToDictionary().ToList(), (w2, kvp) => { w2.Write(kvp.Key); w2.Write(kvp.Value); })); writer.Write( AllowedSurvivingChildProcessNames, (w, v) => w.WriteReadOnlyList(v, (w2, v2) => w2.Write(v2))); writer.Write(MaxLengthInMemory); writer.Write(Timeout, (w, v) => w.Write(v)); writer.Write(NestedProcessTerminationTimeout); writer.Write(PipSemiStableHash); writer.WriteNullableString(TimeoutDumpDirectory); writer.Write((byte)SandboxKind); writer.WriteNullableString(PipDescription); if (SandboxedProcessStandardFiles == null) { SandboxedProcessStandardFiles.From(FileStorage).Serialize(writer); } else { SandboxedProcessStandardFiles.Serialize(writer); } writer.Write(StandardInputSourceInfo, (w, v) => v.Serialize(w)); writer.Write(StandardObserverDescriptor, (w, v) => v.Serialize(w)); // File access manifest should be serialize the last. writer.Write(FileAccessManifest, (w, v) => FileAccessManifest.Serialize(stream)); } }
public void BreakawayProcessIsNotDetoured() { // TODO: doesn't currently work on Linux if (OperatingSystemHelper.IsLinuxOS) { return; } var fam = new FileAccessManifest( Context.PathTable, childProcessesToBreakawayFromSandbox: new[] { TestProcessToolName }) { FailUnexpectedFileAccesses = false, ReportUnexpectedFileAccesses = true, ReportFileAccesses = true }; var srcFile1 = CreateSourceFile(); var srcFile2 = CreateSourceFile(); var info = ToProcessInfo( ToProcess( Operation.ReadFile(srcFile1), Operation.Spawn( Context.PathTable, true, Operation.ReadFile(srcFile2))), fileAccessManifest: fam); var result = RunProcess(info).GetAwaiter().GetResult(); XAssert.AreEqual(0, result.ExitCode); var observedAccesses = result.FileAccesses .Select(reportedAccess => AbsolutePath.TryCreate(Context.PathTable, reportedAccess.GetPath(Context.PathTable), out AbsolutePath result) ? result : AbsolutePath.Invalid) .ToArray(); // We should see the access that happens on the main test process XAssert.Contains(observedAccesses, srcFile1.Path); // We shouldn't see the access that happens on the spawned process XAssert.ContainsNot(observedAccesses, srcFile2.Path); // Only a single process should be reported: the parent one var testProcess = ExcludeInjectedOnes(result.Processes).Single(); XAssert.AreEqual(TestProcessToolName.ToLowerInvariant(), Path.GetFileName(testProcess.Path).ToLowerInvariant()); }
private bool ResolveAccess( FileAccessManifest manifest, ReportedFileAccess access, AbsolutePath accessPath, AbsolutePath resolvedPath, [CanBeNull] string resolvedPathAsString, bool isDirectoryReparsePoint, out ReportedFileAccess resolvedAccess) { var flags = isDirectoryReparsePoint ? access.FlagsAndAttributes | FlagsAndAttributes.FILE_ATTRIBUTE_DIRECTORY : access.FlagsAndAttributes; // If they are different, this means the original path contains symlinks we need to resolve if (resolvedPath != accessPath) { if (access.ManifestPath == resolvedPath) { // When the manifest path matches the path, we don't store the latter resolvedAccess = access.CreateWithPathAndAttributes( null, access.ManifestPath, flags); } else { resolvedPathAsString ??= resolvedPath.ToString(m_context.PathTable); // In this case we need to normalize the manifest path as well // Observe the resolved path is fully resolved, and therefore if a manifest path is found, it will // also be fully resolved // If no manifest is found for the resolved path, the result is invalid, which is precisely what we need in resolvedManifestPath manifest.TryFindManifestPathFor(resolvedPath, out AbsolutePath resolvedManifestPath, out _); resolvedAccess = access.CreateWithPathAndAttributes( resolvedPath == resolvedManifestPath ? null : resolvedPathAsString, resolvedManifestPath, flags); } return(true); } resolvedAccess = access; return(false); }
private ReportedFileAccess GenerateReadAccessForPath(FileAccessManifest manifest, AbsolutePath currentPath, ReportedFileAccess access) { manifest.TryFindManifestPathFor(currentPath, out AbsolutePath manifestPath, out FileAccessPolicy nodePolicy); return(new ReportedFileAccess( ReportedFileOperation.CreateFile, access.Process, RequestedAccess.Read, (nodePolicy & FileAccessPolicy.AllowRead) != 0 ? FileAccessStatus.Allowed : FileAccessStatus.Denied, (nodePolicy & FileAccessPolicy.ReportAccess) != 0, access.Error, Usn.Zero, DesiredAccess.GENERIC_READ, ShareMode.FILE_SHARE_READ, CreationDisposition.OPEN_ALWAYS, FlagsAndAttributes.FILE_ATTRIBUTE_ARCHIVE, manifestPath, manifestPath == currentPath? null : currentPath.ToString(m_context.PathTable), string.Empty)); }
public void AugmentedAccessPathsAreCanonicalized() { var fam = new FileAccessManifest( Context.PathTable, childProcessesToBreakawayFromSandbox: new[] { TestProcessToolName }) { FailUnexpectedFileAccesses = false, ReportUnexpectedFileAccesses = true, ReportFileAccesses = true }; var basePath = TestBinRootPath.Combine(Context.PathTable, "foo").Combine(Context.PathTable, "bar"); // Let's create a path that is equivalent to base path but it is constructed with '..' string nonCanonicalBasePath = Path.Combine(basePath.ToString(Context.PathTable), "..", "bar"); var source = CreateSourceFile(basePath); var output = CreateOutputFileArtifact(basePath); // Now create non-canonical paths for source and output string nonCanonicalSource = Path.Combine(nonCanonicalBasePath, source.Path.GetName(Context.PathTable).ToString(Context.StringTable)); string nonCanonicalOutput = Path.Combine(nonCanonicalBasePath, output.Path.GetName(Context.PathTable).ToString(Context.StringTable)); var collector = new FileAccessDetoursListenerCollector(Context.PathTable); var info = ToProcessInfo( ToProcess( Operation.AugmentedRead(nonCanonicalSource), Operation.AugmentedWrite(nonCanonicalOutput)), fileAccessManifest: fam, detoursListener: collector); var result = RunProcess(info).GetAwaiter().GetResult(); XAssert.AreEqual(0, result.ExitCode); // Let's check the raw paths reported by detours to make sure they are canonicalized var allRawPaths = collector.GetAllFileAccessPaths().Select(path => path.ToUpperInvariant()); XAssert.Contains(allRawPaths, source.Path.ToString(Context.PathTable).ToUpperInvariant(), output.Path.ToString(Context.PathTable).ToUpperInvariant()); }
/// <nodoc /> private FileAccessManifest CreateManifest(AbsolutePath pathToProcess, SandboxOptions option, string workingDir) { var fam = new FileAccessManifest(m_pathTable) { FailUnexpectedFileAccesses = true, ReportFileAccesses = false, ReportUnexpectedFileAccesses = false, MonitorChildProcesses = true, MonitorNtCreateFile = true, MonitorZwCreateOpenQueryFile = true, }; // We make whole filesystem as read-only. fam.AddScope(AbsolutePath.Invalid, FileAccessPolicy.MaskAll, FileAccessPolicy.AllowRead); // Block working dir AddBlockedPath(fam, option, AbsolutePath.Create(m_pathTable, workingDir)); // We explicitly allow reading from the tool path AddReadOnlyPath(fam, option, pathToProcess); // We allow access on all provided files/directories // Note: if C:\A is allowed, its subtree is allowed too. foreach (var path in option.readonly_files) { AddReadOnlyPath(fam, option, path); } foreach (var path in option.writable_files) { AddReadWritePath(fam, option, path); } foreach (var path in option.blocked_files) { AddBlockedPath(fam, option, path); } return(fam); }
/// <inheritdoc /> public bool NotifyPipStarted(LoggingContext loggingContext, FileAccessManifest fam, SandboxedProcessUnix process) { Contract.Requires(process.Started); Contract.Requires(fam.PipId != 0); if (!m_pipProcesses.TryAdd(fam.PipId, process)) { throw new BuildXLException($"Process with PidId {fam.PipId} already exists"); } var setup = new FileAccessSetup() { DllNameX64 = string.Empty, DllNameX86 = string.Empty, ReportPath = process.ExecutableAbsolutePath, // piggybacking on ReportPath to pass full executable path }; using (var wrapper = Pools.MemoryStreamPool.GetInstance()) { var debugFlags = true; ArraySegment <byte> manifestBytes = fam.GetPayloadBytes( loggingContext, setup, wrapper.Instance, timeoutMins: 10, // don't care because on Mac we don't kill the process from the Kext once it times out debugFlagsMatch: ref debugFlags); Contract.Assert(manifestBytes.Offset == 0); var result = Sandbox.SendPipStarted( processId: process.ProcessId, pipId: fam.PipId, famBytes: manifestBytes.Array, famBytesLength: manifestBytes.Count, type: Sandbox.ConnectionType.Kext, info: ref m_kextConnectionInfo); return(result); } }