private void DoReportAccess(
            ReportedFileOperation reportedFileOperation,
            RequestedAccess requestedAccess,
            FileAccessStatus fileAccessStatus,
            uint errorCode,
            Usn usn,
            DesiredAccess desiredAccess,
            ShareMode shareMode,
            CreationDisposition creationDisposition,
            FlagsAndAttributes flagsAndAttributes,
            FlagsAndAttributes openedFileOrDirectoryAttributes,
            string path,
            string enumeratePattern,
            string processArgs)
        {
            var process = Process.GetCurrentProcess();

            Contract.Requires(!string.IsNullOrEmpty(path));
            Contract.Requires(Path.IsPathRooted(path), $"Provided path is expected to be rooted, but found '{path}'.");

            // The given path may not be canonicalized (e.g. it may contain '..')
            string fullPath;

            try
            {
                fullPath = FileUtilities.GetFullPath(path);
            }
            catch (BuildXLException)
            {
                // If getting the full path fails, we follow a behavior similar to what detours does, where the access is ignored
                return;
            }

            string access = FileAccessReportLine.GetReportLineForAugmentedFileAccess(
                reportedFileOperation,
                (uint)process.Id,
                requestedAccess,
                fileAccessStatus,
                errorCode,
                usn,
                desiredAccess,
                shareMode,
                creationDisposition,
                flagsAndAttributes,
                openedFileOrDirectoryAttributes,
                fullPath,
                enumeratePattern,
                processArgs);

            if (!FileUtilities.TryWriteFileSync(m_detoursReportHandle, m_encoding.GetBytes(access), out int nativeErrorCode))
            {
                // Something didn't go as expected. We cannot let the process continue if we failed at reporting an access
                throw new NativeWin32Exception(nativeErrorCode, $"Writing augmented file access report failed. Line: {access}");
            }
        }
        private void DoReportAccess(
            ReportedFileOperation reportedFileOperation,
            RequestedAccess requestedAccess,
            FileAccessStatus fileAccessStatus,
            uint errorCode,
            Usn usn,
            DesiredAccess desiredAccess,
            ShareMode shareMode,
            CreationDisposition creationDisposition,
            FlagsAndAttributes flagsAndAttributes,
            string path,
            string enumeratePattern,
            string processArgs)
        {
            var process = Process.GetCurrentProcess();

            string access = FileAccessReportLine.GetReportLineForAugmentedFileAccess(
                reportedFileOperation,
                (uint)process.Id,
                requestedAccess,
                fileAccessStatus,
                errorCode,
                usn,
                desiredAccess,
                shareMode,
                creationDisposition,
                flagsAndAttributes,
                path,
                enumeratePattern,
                processArgs);

            if (!FileUtilities.TryWriteFileSync(m_detoursReportHandle, m_encoding.GetBytes(access), out int nativeErrorCode))
            {
                // Something didn't go as expected. We cannot let the process continue if we failed at reporting an access
                throw new NativeWin32Exception(nativeErrorCode, $"Writing augmented file access report failed. Line: {access}");
            }
        }
Example #3
0
        private bool TryParseAugmentedFileAccess(
            ref string data,
            out uint processId,
            out ReportedFileOperation operation,
            out RequestedAccess requestedAccess,
            out FileAccessStatus status,
            out bool explicitlyReported,
            out uint error,
            out Usn usn,
            out DesiredAccess desiredAccess,
            out ShareMode shareMode,
            out CreationDisposition creationDisposition,
            out FlagsAndAttributes flagsAndAttributes,
            out AbsolutePath manifestPath,
            out string path,
            out string enumeratePattern,
            out string processArgs,
            out string errorMessage)
        {
            // An augmented file access has the same structure as a regular one, so let's call
            // the usual parser
            var result = FileAccessReportLine.TryParse(
                ref data,
                out processId,
                out operation,
                out requestedAccess,
                out status,
                out explicitlyReported,
                out error,
                out usn,
                out desiredAccess,
                out shareMode,
                out creationDisposition,
                out flagsAndAttributes,
                out manifestPath,
                out path,
                out enumeratePattern,
                out processArgs,
                out errorMessage);

            // Augmented file accesses never have the manifest path set, since there is no easy access to the manifest for
            // processes to use.
            // Let's recreate the manifest path based on the current path and manifest
            // The manifest may have its own path table after deserialization, so make sure we use the right one
            if (string.IsNullOrEmpty(path) || !AbsolutePath.TryCreate(m_manifest.PathTable, path, out var absolutePath))
            {
                return(result);
            }

            var success = m_manifest.TryFindManifestPathFor(absolutePath, out AbsolutePath computedManifestPath, out FileAccessPolicy policy);

            // If the manifest specified to not report any accesses, then we just ignore this report line
            // We could impose trusted tools the responsibility of knowing this (and not reporting these accesses), but
            // this type of coordination is hard to achieve
            if ((policy & FileAccessPolicy.ReportAccess) == 0)
            {
                path = null;
                return(true);
            }

            // If there is no explicit policy for this path, just keep the manifest path as it came from the report
            if (!success)
            {
                return(result);
            }

            manifestPath = computedManifestPath;

            return(result);
        }
Example #4
0
        private bool FileAccessReportLineReceived(string data, out string errorMessage)
        {
            Contract.Assume(!IsFrozen, "FileAccessReportLineReceived: !IsFrozen");

            if (!FileAccessReportLine.TryParse(
                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;
            }

            // 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 == null ? manifestPath.ToString(m_pathTable) : 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;
            }

            // Special case seen with vstest.console.exe
            if (path.Length == 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);
                    if (process != null)
                    {
                        m_activeProcesses.Remove(processId);
                        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),
            // it's ok to just create a new one since ReportedProcess is used for descriptive purposes only
            if (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;
            }

            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;
                }
            }

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

            HandleReportedAccess(reportedAccess);
            return true;
        }