Пример #1
0
        /// <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);
        }
Пример #2
0
        /// <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);
        }
Пример #3
0
 /// <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);
 }
Пример #4
0
        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);
        }
Пример #6
0
        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);
        }
Пример #7
0
        /// <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;
        }
Пример #8
0
        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;
        }
Пример #9
0
 /// <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);
 }
Пример #10
0
        /// <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);
            }
        }
Пример #11
0
        /// <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),
            });
        }
Пример #12
0
        /// <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;
        }
Пример #13
0
        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);
        }
Пример #14
0
        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);
        }
Пример #15
0
        /// <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);
            }
        }
Пример #16
0
 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();
            }
        }
Пример #18
0
        /// <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);
        }
Пример #19
0
        /// <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);
        }
Пример #20
0
        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);
            }
        }
Пример #21
0
        /// <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));
        }
Пример #22
0
        /// <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);
        }
Пример #23
0
        /// <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);
        }
Пример #24
0
        /// <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());
        }
Пример #26
0
        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);
        }
Пример #27
0
        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));
        }
Пример #28
0
        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());
        }
Пример #29
0
        /// <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);
        }
Пример #30
0
        /// <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);
            }
        }