Пример #1
0
        /// <summary>
        /// An alternative to <see cref="ReportLineReceived(string)"/> for reporting file accesses
        /// </summary>
        public bool ReportFileAccess <T>(ref T accessReport, FileAccessReportProvider <T> parser)
        {
            var result = FileAccessReportLineReceived(ref accessReport, parser, isAnAugmentedFileAccess: false, out var errorMessage);

            if (!result)
            {
                MessageProcessingFailure = CreateMessageProcessingFailure(errorMessage);
            }

            return(result);
        }
Пример #2
0
        private bool FileAccessReportLineReceived <T>(ref T data, FileAccessReportProvider <T> parser, bool isAnAugmentedFileAccess, out string errorMessage)
        {
            Contract.Assume(!IsFrozen, "FileAccessReportLineReceived: !IsFrozen");

            if (!parser(
                    ref data,
                    out var processId,
                    out var operation,
                    out var requestedAccess,
                    out var status,
                    out var explicitlyReported,
                    out var error,
                    out var usn,
                    out var desiredAccess,
                    out var shareMode,
                    out var creationDisposition,
                    out var flagsAndAttributes,
                    out var manifestPath,
                    out var path,
                    out var enumeratePattern,
                    out var processArgs,
                    out errorMessage))
            {
                return(false);
            }

            // Special case seen with vstest.console.exe
            if (string.IsNullOrEmpty(path))
            {
                return(true);
            }

            // If there is a listener registered and notifications allowed, notify over the interface.
            if (m_detoursEventListener != null && (m_detoursEventListener.GetMessageHandlingFlags() & MessageHandlingFlags.FileAccessNotify) != 0)
            {
                m_detoursEventListener.HandleFileAccess(
                    PipSemiStableHash,
                    PipDescription,
                    operation,
                    requestedAccess,
                    status,
                    explicitlyReported,
                    processId,
                    error,
                    desiredAccess,
                    shareMode,
                    creationDisposition,
                    flagsAndAttributes,
                    path,
                    processArgs);
            }

            // If there is a listener registered that disables the collection of data in the collections, just exit.
            if (m_detoursEventListener != null && (m_detoursEventListener.GetMessageHandlingFlags() & MessageHandlingFlags.FileAccessCollect) == 0)
            {
                return(true);
            }

            if (m_manifest.DirectoryTranslator != null)
            {
                path = m_manifest.DirectoryTranslator.Translate(path);
            }

            // If we are getting a message for ChangedReadWriteToReadAccess operation,
            // just log it as a warning and return
            if (operation == ReportedFileOperation.ChangedReadWriteToReadAccess)
            {
                Tracing.Logger.Log.ReadWriteFileAccessConvertedToReadMessage(m_loggingContext, PipSemiStableHash, PipDescription, processId, path);
                HasReadWriteToReadFileAccessRequest = true;
                return(true);
            }

            // A process id is only unique during the lifetime of the process (so there may be duplicates reported),
            // but the ID is at least consistent with other tracing tools including procmon.
            // For the purposes of event correlation, m_activeProcesses keeps track which process id maps to which process a the current time.
            // We also record all processes in a list. Because process create and exit messages can arrive out of order on macOS when multiple queues
            // are used, we have to keep track of the reported exits using the m_processesExits dictionary.
            ReportedProcess process;

            if (operation == ReportedFileOperation.Process)
            {
                process = new ReportedProcess(processId, path, processArgs);
                m_activeProcesses[processId] = process;
                Processes.Add(process);
            }
            else
            {
                m_activeProcesses.TryGetValue(processId, out process);

                if (operation == ReportedFileOperation.ProcessExit)
                {
                    AddLookupEntryForProcessExit(processId, process);
                    if (process != null)
                    {
                        m_activeProcesses.TryRemove(processId, out _);
                        path = process.Path;
                    }
                    else
                    {
                        // no process to remove;
                        return(true);
                    }
                }
            }

            // This assertion doesn't have to hold when using /sandboxKind:macOsKext because some messages may come out of order
            Contract.Assert(OperatingSystemHelper.IsUnixOS || process != null, "Should see a process creation before its accesses (malformed report)");

            // If no active ReportedProcess is found (e.g., because it already completed but we are still processing its access reports),
            // try to see the latest exiting process with the same process id. Otherwise, it's ok to just create an unnamed one since ReportedProcess is used for descriptive purposes only
            if (process == null && (!m_processesExits.TryGetValue(processId, out process) || process == null))
            {
                process = new ReportedProcess(processId, string.Empty, string.Empty);
            }

            // For exact matches (i.e., not a scope rule), the manifest path is the same as the full path.
            // In that case we don't want to keep carrying around the giant string.
            if (AbsolutePath.TryGet(m_pathTable, path, out AbsolutePath finalPath) && finalPath == manifestPath)
            {
                path = null;
            }

            var pathAsAbsolutePath = finalPath;

            if (!pathAsAbsolutePath.IsValid)
            {
                AbsolutePath.TryCreate(m_pathTable, path, out pathAsAbsolutePath);
            }

            if (pathAsAbsolutePath.IsValid && m_sharedOpaqueOutputLogger != null && (requestedAccess & RequestedAccess.Write) != 0)
            {
                m_sharedOpaqueOutputLogger.RecordFileWrite(m_pathTable, pathAsAbsolutePath);
            }

            Contract.Assume(manifestPath.IsValid || !string.IsNullOrEmpty(path));

            if (path != null)
            {
                if (m_pathCache.TryGetValue(path, out var cachedPath))
                {
                    path = cachedPath;
                }
                else
                {
                    m_pathCache[path] = path;
                }
            }

            if (operation == ReportedFileOperation.FirstAllowWriteCheckInProcess)
            {
                // This operation represents that a given path was checked for write access for the first time
                // within the scope of a process. The status of the operation represents whether that access should
                // have been allowed/denied, based on the existence of the file.
                // However, we need to determine whether to deny the access based on the first time the path was
                // checked for writes across the whole process tree. This means checking the first time this operation
                // is reported for a given path, and ignore subsequent reports.
                // Races are ignored: a race means two child processes are racing to create or delete the same file
                // - something that is not a good build behavior anyway - and the outcome will be that we will
                // non-deterministically deny the access
                // We store the path as an absolute path in order to guarantee canonicalization: e.g. prefixes like \\?\
                // are not canonicalized in detours
                if (path != null && pathAsAbsolutePath.IsValid && !m_overrideAllowedWritePaths.ContainsKey(pathAsAbsolutePath))
                {
                    // We should override write allowed accesses for this path if the status of the special operation was 'denied'
                    m_overrideAllowedWritePaths[pathAsAbsolutePath] = (status == FileAccessStatus.Denied);
                }

                return(true);
            }

            // If this is an augmented file access, the method was not based on policy, but a trusted tool reported the access
            FileAccessStatusMethod method = isAnAugmentedFileAccess?
                                            FileAccessStatusMethod.TrustedTool :
                                            FileAccessStatusMethod.PolicyBased;

            // If we are processing an allowed write, but this should be overridden based on file existence,
            // we change the status here
            // Observe that if the access is coming from a trusted tool, that trumps file existence and we don't deny the access
            if (method != FileAccessStatusMethod.TrustedTool &&
                path != null &&
                (requestedAccess & RequestedAccess.Write) != 0 &&
                status == FileAccessStatus.Allowed &&
                m_overrideAllowedWritePaths.Count > 0 && // Avoid creating the absolute path if the override allowed writes flag is off
                pathAsAbsolutePath.IsValid &&
                m_overrideAllowedWritePaths.TryGetValue(pathAsAbsolutePath, out bool shouldOverrideAllowedAccess) &&
                shouldOverrideAllowedAccess)
            {
                status = FileAccessStatus.Denied;
                method = FileAccessStatusMethod.FileExistenceBased;
            }

            var reportedAccess =
                new ReportedFileAccess(
                    operation,
                    process,
                    requestedAccess,
                    status,
                    explicitlyReported,
                    error,
                    usn,
                    desiredAccess,
                    shareMode,
                    creationDisposition,
                    flagsAndAttributes,
                    manifestPath,
                    path,
                    enumeratePattern,
                    method);

            HandleReportedAccess(reportedAccess);
            return(true);
        }