Exemple #1
0
 public ReportedFileAccess(
     ReportedFileOperation operation,
     ReportedProcess process,
     RequestedAccess requestedAccess,
     FileAccessStatus status,
     bool explicitlyReported,
     uint error,
     Usn usn,
     DesiredAccess desiredAccess,
     ShareMode shareMode,
     CreationDisposition creationDisposition,
     FlagsAndAttributes flagsAndAttributes,
     AbsolutePath manifestPath,
     string path,
     string enumeratePatttern)
 {
     Contract.Requires(process != null);
     Operation          = operation;
     Process            = process;
     RequestedAccess    = requestedAccess;
     Status             = status;
     ExplicitlyReported = explicitlyReported;
     Error               = error;
     Usn                 = usn;
     DesiredAccess       = desiredAccess;
     ShareMode           = shareMode;
     CreationDisposition = creationDisposition;
     FlagsAndAttributes  = flagsAndAttributes;
     ManifestPath        = manifestPath;
     Path                = path;
     EnumeratePattern    = enumeratePatttern;
 }
Exemple #2
0
        public static ReportedFileAccess Create(
            ReportedFileOperation operation,
            ReportedProcess process,
            RequestedAccess requestedAccess,
            FileAccessStatus status,
            bool explicitlyReported,
            uint error,
            Usn usn,
            DesiredAccess desiredAccess,
            ShareMode shareMode,
            CreationDisposition creationDisposition,
            FlagsAndAttributes flagsAndAttributes,
            FlagsAndAttributes openedFileOrDirectoryAttribute,
            PathTable pathTable,
            string path,
            string enumeratePattern = null)
        {
            Contract.Requires(process != null);
            Contract.Requires(pathTable != null);
            Contract.Requires(path != null);

            AbsolutePath absolutePath;

            if (AbsolutePath.TryGet(pathTable, (StringSegment)path, out absolutePath))
            {
                return(new ReportedFileAccess(
                           operation,
                           process,
                           requestedAccess,
                           status,
                           explicitlyReported,
                           error,
                           usn,
                           desiredAccess,
                           shareMode,
                           creationDisposition,
                           flagsAndAttributes,
                           openedFileOrDirectoryAttribute,
                           absolutePath,
                           null,
                           enumeratePattern));
            }

            return(new ReportedFileAccess(
                       operation,
                       process,
                       requestedAccess,
                       status,
                       explicitlyReported,
                       error,
                       usn,
                       desiredAccess,
                       shareMode,
                       creationDisposition,
                       flagsAndAttributes,
                       openedFileOrDirectoryAttribute,
                       AbsolutePath.Invalid,
                       path,
                       enumeratePattern));
        }
        public static ReportedFileAccess Create(
            ReportedFileOperation operation,
            ReportedProcess process,
            RequestedAccess requestedAccess,
            FileAccessStatus status,
            bool explicitlyReported,
            uint error,
            Usn usn,
            DesiredAccess desiredAccess,
            ShareMode shareMode,
            CreationDisposition creationDisposition,
            FlagsAndAttributes flagsAndAttributes,
            AbsolutePath path,
            string enumeratePattern = null)
        {
            Contract.Requires(process != null);

            return(new ReportedFileAccess(
                       operation,
                       process,
                       requestedAccess,
                       status,
                       explicitlyReported,
                       error,
                       usn,
                       desiredAccess,
                       shareMode,
                       creationDisposition,
                       flagsAndAttributes,
                       path,
                       null,
                       enumeratePattern));
        }
Exemple #4
0
        /// <inheritdoc/>
        public override void HandleFileAccess(long pipId, string pipDescription, ReportedFileOperation operation, RequestedAccess requestedAccess, FileAccessStatus status, bool explicitlyReported, uint processId, uint error, DesiredAccess desiredAccess, ShareMode shareMode, CreationDisposition creationDisposition, FlagsAndAttributes flagsAndAttributes, string path, string processArgs)
        {
            if (AbsolutePath.TryCreate(m_pathTable, path, out AbsolutePath absolutePath))
            {
                m_fileAccessPaths.Add(absolutePath);
            }

            m_allFileAccessPaths.Add(path);
        }
        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}");
            }
        }
 public VerifiedCorrelation(
     string file,
     ReportedFileOperation operation,
     string correlatedFile,
     ReportedFileOperation correlatedOperation)
 {
     File                = file;
     Operation           = operation;
     CorrelatedFile      = correlatedFile;
     CorrelatedOperation = correlatedOperation;
 }
        /// <summary>
        /// Returns a detours report line representing an augmented file access.
        /// </summary>
        /// <remarks>
        /// The line format is compatible with a regular file access report line. This is not a hard requirement (since <see cref="ReportType.AugmentedFileAccess"/>
        /// is used to distinguish this line from regular lines), but is convenient to use the same report line parser for all cases. So future changes
        /// can happen here as long as they are kept in sync with <see cref="SandboxedProcessReports.TryParseAugmentedFileAccess"/>
        /// </remarks>
        internal static string GetReportLineForAugmentedFileAccess(
            ReportedFileOperation reportedFileOperation,
            uint processId,
            RequestedAccess requestedAccess,
            FileAccessStatus fileAccessStatus,
            uint errorCode,
            Usn usn,
            DesiredAccess desiredAccess,
            ShareMode shareMode,
            CreationDisposition creationDisposition,
            FlagsAndAttributes flagsAndAttributes,
            FlagsAndAttributes openedFileOrDirectoryAttributes,
            string absolutePath,
            [CanBeNull] string enumeratePattern,
            [CanBeNull] string processArgs)
        {
            var result = new System.Text.StringBuilder();

            result.Append($"{(int)ReportType.AugmentedFileAccess},{reportedFileOperation}:");
            result.Append($"{processId:x}|");
            result.Append($"{SandboxedProcessReports.FileAccessNoId:x}|"); // no id.
            result.Append($"{SandboxedProcessReports.FileAccessNoId:x}|"); // no correlation id.
            result.Append($"{(byte)requestedAccess:x}|");
            result.Append($"{(byte)fileAccessStatus:x}|");
            // '1' makes the access look as explicitly reported, but this actually doesn't matter since it will get
            // set based on the manifest policy upon reception
            result.Append("1|");
            result.Append($"{errorCode:x}|");
            result.Append($"{usn.Value:x}|");
            result.Append($"{(uint)desiredAccess:x}|");
            result.Append($"{(uint)shareMode:x}|");
            result.Append($"{(uint)creationDisposition:x}|");
            result.Append($"{(uint)flagsAndAttributes:x}|");
            result.Append($"{(uint)openedFileOrDirectoryAttributes:x}|");
            // The manifest path is always written as invalid
            result.Append($"{AbsolutePath.Invalid.Value.Value:x}|");
            result.Append(absolutePath);

            if (!string.IsNullOrEmpty(enumeratePattern))
            {
                Contract.Assert(requestedAccess == RequestedAccess.Enumerate);
                result.Append($"|{enumeratePattern}");
            }

            if (!string.IsNullOrEmpty(processArgs))
            {
                Contract.Assert(reportedFileOperation == ReportedFileOperation.Process);
                result.Append($"|{processArgs}");
            }

            return(result.Append("\r\n").ToString());
        }
Exemple #8
0
        private bool ReportProvider(
            ref AccessReport report, 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)
        {
            var errorMessages = new List <string>();

            checked
            {
                processId = (uint)report.Pid;

                if (!SandboxedProcessReports.FileAccessReportLine.Operations.TryGetValue(report.DecodeOperation(), out operation))
                {
                    errorMessages.Add($"Unknown operation '{report.DecodeOperation()}'");
                }

                requestedAccess = (RequestedAccess)report.RequestedAccess;
                if (report.RequestedAccess > s_maxRequestedAccess)
                {
                    errorMessages.Add($"Illegal value for 'RequestedAccess': {requestedAccess}; maximum allowed: {(int)RequestedAccess.All}");
                }

                status = (FileAccessStatus)report.Status;
                if (report.Status > s_maxFileAccessStatus)
                {
                    errorMessages.Add($"Illegal value for 'Status': {status}");
                }

                bool isWrite = (report.RequestedAccess & (byte)RequestedAccess.Write) != 0;

                explicitlyReported = report.ExplicitLogging > 0;
                error               = report.Error;
                usn                 = ReportedFileAccess.NoUsn;
                desiredAccess       = isWrite ? DesiredAccess.GENERIC_WRITE : DesiredAccess.GENERIC_READ;
                shareMode           = ShareMode.FILE_SHARE_READ;
                creationDisposition = CreationDisposition.OPEN_ALWAYS;
                flagsAndAttributes  = 0;
                path                = report.Path;
                enumeratePattern    = string.Empty;
                processArgs         = string.Empty;

                AbsolutePath.TryCreate(PathTable, path, out manifestPath);

                errorMessage = errorMessages.Any()
                    ? $"Illegal access report: '{AccessReportToString(report)}' :: {string.Join(";", errorMessages)}"
                    : string.Empty;

                return(errorMessage == string.Empty);
            }
        }
Exemple #9
0
 /// <summary>
 /// Called to handle FileAccess message.
 /// </summary>
 /// <param name="pipId">The pip id</param>
 /// <param name="pipDescription">The pip descruption</param>
 /// <param name="operation">The operation</param>
 /// <param name="requestedAccess">The requested access</param>
 /// <param name="status">The status of the access request</param>
 /// <param name="explicitlyReported">Is it an explicit report</param>
 /// <param name="processId">The process ID that made the access.</param>
 /// <param name="error">Error code of the operation</param>
 /// <param name="desiredAccess">The desired access</param>
 /// <param name="shareMode">The share mode</param>
 /// <param name="creationDisposition">The creation disposition</param>
 /// <param name="flagsAndAttributes">The flags and attributes</param>
 /// <param name="path">The path being accessed</param>
 /// <param name="processArgs">The process arguments</param>
 public abstract void HandleFileAccess(
     long pipId,
     string pipDescription,
     ReportedFileOperation operation,
     RequestedAccess requestedAccess,
     FileAccessStatus status,
     bool explicitlyReported,
     uint processId,
     uint error,
     DesiredAccess desiredAccess,
     ShareMode shareMode,
     CreationDisposition creationDisposition,
     FlagsAndAttributes flagsAndAttributes,
     string path,
     string processArgs);
Exemple #10
0
 /// <summary>
 /// Creates an expected reported access from <see cref="RemoteApiDetoursTestBase.RunRemoteApiInSandboxAsync" />.
 /// This is a projection of key fields of <see cref="ReportedFileAccess" />.
 /// </summary>
 protected ExpectedReport ExpectReport(
     ReportedFileOperation operation,
     RequestedAccess access,
     AbsolutePath path,
     FileAccessStatus status = FileAccessStatus.Allowed,
     bool exists             = true)
 {
     return(new ExpectedReport
     {
         Path = path,
         Operation = operation,
         RequestedAccess = access,
         Status = status,
         Exists = exists
     });
 }
Exemple #11
0
        /// <summary>
        /// Returns a detours report line representing an augmented file access.
        /// </summary>
        /// <remarks>
        /// The line format is compatible with a regular file access report line. This is not a hard requirement (since <see cref="ReportType.AugmentedFileAccess"/>
        /// is used to distinguish this line from regular lines), but is convenient to use the same report line parser for all cases. So future changes
        /// can happen here as long as they are kept in sync with <see cref="SandboxedProcessReports.TryParseAugmentedFileAccess"/>
        /// </remarks>
        internal static string GetReportLineForAugmentedFileAccess(
            ReportedFileOperation reportedFileOperation,
            uint processId,
            RequestedAccess requestedAccess,
            FileAccessStatus fileAccessStatus,
            uint errorCode,
            Usn usn,
            DesiredAccess desiredAccess,
            ShareMode shareMode,
            CreationDisposition creationDisposition,
            FlagsAndAttributes flagsAndAttributes,
            string absolutePath,
            [CanBeNull] string enumeratePattern,
            [CanBeNull] string processArgs)
        {
            var result = new System.Text.StringBuilder();

            result.Append($"{(int)ReportType.AugmentedFileAccess},{reportedFileOperation}:");
            result.Append($"{processId.ToString("x")}|");
            result.Append($"{((byte)requestedAccess).ToString("x")}|");
            result.Append($"{((byte)fileAccessStatus).ToString("x")}|");
            // '1' makes the access look as explicitly reported
            result.Append("1|");
            result.Append($"{errorCode.ToString("x")}|");
            result.Append($"{usn.Value.ToString("x")}|");
            result.Append($"{((uint)desiredAccess).ToString("x")}|");
            result.Append($"{((uint)shareMode).ToString("x")}|");
            result.Append($"{((uint)creationDisposition).ToString("x")}|");
            result.Append($"{((uint)flagsAndAttributes).ToString("x")}|");
            // The manifest path is always written as invalid
            result.Append($"{AbsolutePath.Invalid.Value.Value.ToString("x")}|");
            result.Append(absolutePath);

            if (!string.IsNullOrEmpty(enumeratePattern))
            {
                Contract.Assert(requestedAccess == RequestedAccess.Enumerate);
                result.Append($"|{enumeratePattern}");
            }

            if (!string.IsNullOrEmpty(processArgs))
            {
                Contract.Assert(reportedFileOperation == ReportedFileOperation.Process);
                result.Append($"|{processArgs}");
            }

            return(result.Append("\r\n").ToString());
        }
Exemple #12
0
        private bool ShouldNotResolveLastSegment(ExpandedAbsolutePath accessPath, ReportedFileOperation operation, FlagsAndAttributes flagsAndAttributes, out bool isReparsePoint)
        {
            isReparsePoint = IsDirectorySymlinkOrJunctionWithCache(accessPath);
            // The following operations act on the reparse point directly, not on the target
            // TODO: the logic is not perfect but good enough. The detours implementation will replace
            // this eventually
            var operationOnReparsePoint =
                (operation == ReportedFileOperation.CreateHardLinkSource ||
                 operation == ReportedFileOperation.CreateSymbolicLinkSource ||
                 operation == ReportedFileOperation.GetFileAttributes ||
                 operation == ReportedFileOperation.GetFileAttributesEx ||
                 operation == ReportedFileOperation.DeleteFile ||
                 (operation == ReportedFileOperation.CreateFile && (flagsAndAttributes & FlagsAndAttributes.FILE_FLAG_OPEN_REPARSE_POINT) != 0) ||
                 (operation == ReportedFileOperation.NtCreateFile && (flagsAndAttributes & FlagsAndAttributes.FILE_FLAG_OPEN_REPARSE_POINT) != 0) ||
                 (operation == ReportedFileOperation.ZwCreateFile && (flagsAndAttributes & FlagsAndAttributes.FILE_FLAG_OPEN_REPARSE_POINT) != 0) ||
                 (operation == ReportedFileOperation.ZwOpenFile && (flagsAndAttributes & FlagsAndAttributes.FILE_FLAG_OPEN_REPARSE_POINT) != 0));

            return(operationOnReparsePoint && isReparsePoint);
        }
        /// <summary>
        /// Attempts to report a file access to BuildXL without actually performing any IO
        /// </summary>
        /// <remarks>
        /// Failure means this method was invoked from a process that was not configured to breakaway from the sandbox.
        /// The provided path is required to be non-null and rooted
        /// </remarks>
        public bool TryReportAugmentedFileAccess(
            ReportedFileOperation reportedFileOperation,
            RequestedAccess requestedAccess,
            FileAccessStatus fileAccessStatus,
            uint errorCode,
            Usn usn,
            DesiredAccess desiredAccess,
            ShareMode shareMode,
            CreationDisposition creationDisposition,
            FlagsAndAttributes flagsAndAttributes,
            FlagsAndAttributes openedFileOrDirectoryAttributes,
            string path,
            [CanBeNull] string enumeratePattern = null,
            [CanBeNull] string processArgs      = null)
        {
            Contract.Requires(!string.IsNullOrEmpty(path));

            if (m_detoursReportHandle == null)
            {
                return(false);
            }

            DoReportAccess(
                reportedFileOperation,
                requestedAccess,
                fileAccessStatus,
                errorCode,
                usn,
                desiredAccess,
                shareMode,
                creationDisposition,
                flagsAndAttributes,
                openedFileOrDirectoryAttributes,
                path,
                enumeratePattern,
                processArgs);

            return(true);
        }
        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}");
            }
        }
Exemple #15
0
        private AbsolutePath ResolvePathWithCache(
            AbsolutePath accessPath,
            [CanBeNull] string accessPathAsString,
            ReportedFileOperation operation,
            FlagsAndAttributes flagsAndAttributes,
            [CanBeNull] out string resolvedPathAsString,
            out bool isDirectoryReparsePoint)
        {
            accessPathAsString ??= accessPath.ToString(m_context.PathTable);
            // If the final segment is a directory reparse point and the operation acts on it, don't resolve it
            if (ShouldNotResolveLastSegment(ExpandedAbsolutePath.CreateUnsafe(accessPath, accessPathAsString), operation, flagsAndAttributes, out isDirectoryReparsePoint))
            {
                AbsolutePath resolvedParentPath = ResolvePathWithCache(accessPath.GetParent(m_context.PathTable), accessPathAsString: null, operation, flagsAndAttributes, out string resolvedParentPathAsString, out _);
                PathAtom     name = accessPath.GetName(m_context.PathTable);
                resolvedPathAsString = resolvedParentPathAsString != null?Path.Combine(resolvedParentPathAsString, name.ToString(m_context.StringTable)) : null;

                return(resolvedParentPath.Combine(m_context.PathTable, name));
            }

            // Check the cache
            var cachedResult = m_resolvedPathCache.TryGet(accessPath);

            if (cachedResult.IsFound)
            {
                // Observe we don't cache expanded paths since that might cause the majority of all the paths of a build to live
                // in memory for the whole execution time. So in case of a cache hit, we just return the absolute path. The expanded
                // path needs to be reconstructed as needed.
                resolvedPathAsString = null;
                return(cachedResult.Item.Value);
            }

            // If not found and the path is not a reparse point, check the cache for its parent.
            // Many files may have the same parent, and since the path is not a reparse point we know that
            // resolve(parent\segment) == resolve(parent)\segment
            var parentPath = accessPath.GetParent(m_context.PathTable);

            if (parentPath.IsValid && !isDirectoryReparsePoint && m_resolvedPathCache.TryGet(parentPath) is var cachedParent && cachedParent.IsFound)
            {
                var lastFragment = accessPath.GetName(m_context.PathTable);
                resolvedPathAsString = null;
                AbsolutePath cachedParentPath = cachedParent.Item.Value;
                return(cachedParentPath.IsValid ?
                       cachedParentPath.Combine(m_context.PathTable, lastFragment) :
                       AbsolutePath.Create(m_context.PathTable, lastFragment.ToString(m_context.StringTable)));
            }

            // The cache didn't have it, so let's resolve it
            if (!TryResolveSymlinkedPath(accessPathAsString, out ExpandedAbsolutePath resolvedExpandedPath))
            {
                // If we cannot get the final path (e.g. file not found causes this), then we assume the path
                // is already canonicalized

                // Observe we cannot update the cache since the path was not resolved.
                // TODO: we could consider always trying to resolve the parent

                resolvedPathAsString = accessPathAsString;

                return(accessPath);
            }

            // Update the cache
            m_resolvedPathCache.TryAdd(accessPath, resolvedExpandedPath.Path);

            // If the path is not a reparse point, update the parent as well
            if (!isDirectoryReparsePoint && parentPath.IsValid)
            {
                // Observe we may be storing an invalid path if the resolved path does not have a parent.
                m_resolvedPathCache.TryAdd(parentPath, resolvedExpandedPath.Path.GetParent(m_context.PathTable));
            }

            // If the resolved path is the same as the access path, then we know its last fragment is not a reparse point.
            // So we might just update the symlink cache as well and hopefully speed up subsequent requests to generate read accesses
            // for intermediate symlink dirs
            if (accessPath == resolvedExpandedPath.Path)
            {
                while (parentPath.IsValid && m_symlinkCache.TryAdd(parentPath, false))
                {
                    parentPath = parentPath.GetParent(m_context.PathTable);
                }
            }

            resolvedPathAsString = resolvedExpandedPath.ExpandedPath;
            return(resolvedExpandedPath.Path);
        }
Exemple #16
0
        internal static bool TryParse(
            ref string line,
            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 absolutePath,
            out string path,
            out string enumeratePattern,
            out string processArgs,
            out string errorMessage)
        {
            // TODO: Task 138817: Refactor passing and parsing of report data from native to managed code

            operation           = ReportedFileOperation.Unknown;
            requestedAccess     = RequestedAccess.None;
            status              = FileAccessStatus.None;
            processId           = error = 0;
            usn                 = default;
            explicitlyReported  = false;
            desiredAccess       = 0;
            shareMode           = ShareMode.FILE_SHARE_NONE;
            creationDisposition = 0;
            flagsAndAttributes  = 0;
            absolutePath        = AbsolutePath.Invalid;
            path                = null;
            enumeratePattern    = null;
            processArgs         = null;
            errorMessage        = string.Empty;

            var i     = line.IndexOf(':');
            var index = 0;

            if (i > 0)
            {
                var items = line.Substring(i + 1).Split('|');

                if (!Operations.TryGetValue(line.Substring(0, i), out operation))
                {
                    // We could consider the report line malformed in this case; but in practice it is easy to forget to update this parser
                    // after adding a new call. So let's be conservative about throwing the line out so long as we can parse the important bits to follow.
                    operation = ReportedFileOperation.Unknown;
                }

                // When the command line arguments of the process are not reported there will be 12 fields
                // When command line arguments are included, everything after the 12th field is the command line argument
                // Command line arguments are only reported when the reported file operation is Process
                if (operation == ReportedFileOperation.Process)
                {
                    // Make sure the formatting happens only if the condition is false.
                    if (items.Length < 12)
                    {
                        errorMessage = I($"Unexpected message items (potentially due to pipe corruption) for {operation.ToString()} operation. Message '{line}'. Expected >= 12 items, Received {items.Length} items");
                        return(false);
                    }
                }
                else
                {
                    // An ill behaved tool can try to do GetFileAttribute on a file with '|' char. This will result in a failure of the API, but we get a report for the access.
                    // Allow that by handling such case.
                    // In Office build there is a call to GetFileAttribute with a small xml document as a file name.
                    if (items.Length < 12)
                    {
                        errorMessage = I($"Unexpected message items (potentially due to pipe corruption) for {operation.ToString()} operation. Message '{line}'. Expected >= 12 items, Received {items.Length} items");
                        return(false);
                    }
                }

                if (
                    uint.TryParse(items[index++], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out processId) &&
                    uint.TryParse(items[index++], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var requestedAccessValue) &&
                    uint.TryParse(items[index++], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var statusValue) &&
                    uint.TryParse(items[index++], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var explicitlyReportedValue) &&
                    uint.TryParse(items[index++], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out error) &&
                    ulong.TryParse(items[index++], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var usnValue) &&
                    uint.TryParse(items[index++], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var desiredAccessValue) &&
                    uint.TryParse(items[index++], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var shareModeValue) &&
                    uint.TryParse(items[index++], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var creationDispositionValue) &&
                    uint.TryParse(items[index++], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var flagsAndAttributesValue) &&
                    uint.TryParse(items[index++], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var absolutePathValue))
                {
                    if (statusValue > (uint)FileAccessStatus.CannotDeterminePolicy)
                    {
                        errorMessage = I($"Unknown file access status '{statusValue}'");
                        return(false);
                    }

                    if (requestedAccessValue > (uint)RequestedAccess.All)
                    {
                        errorMessage = I($"Unknown requested access '{requestedAccessValue}'");
                        return(false);
                    }

                    requestedAccess     = (RequestedAccess)requestedAccessValue;
                    status              = (FileAccessStatus)statusValue;
                    explicitlyReported  = explicitlyReportedValue != 0;
                    desiredAccess       = (DesiredAccess)desiredAccessValue;
                    shareMode           = (ShareMode)shareModeValue;
                    creationDisposition = (CreationDisposition)creationDispositionValue;
                    flagsAndAttributes  = (FlagsAndAttributes)flagsAndAttributesValue;
                    absolutePath        = new AbsolutePath(unchecked ((int)absolutePathValue));
                    path = items[index++];
                    // Detours is only guaranteed to sent at least 12 items, so here (since we are at index 12), we must check if this item is included
                    enumeratePattern = index < items.Length ? items[index++] : null;

                    if (requestedAccess != RequestedAccess.Enumerate)
                    {
                        // If the requested access is not enumeration, enumeratePattern does not matter.
                        enumeratePattern = null;
                    }

                    if ((operation == ReportedFileOperation.Process) && (items.Length > index))
                    {
                        processArgs = items[index++];
                        while (index < items.Length)
                        {
                            processArgs += "|";
                            processArgs += items[index++];
                        }
                    }
                    else
                    {
                        processArgs = string.Empty;
                    }

                    usn = new Usn(usnValue);
                    Contract.Assert(index <= items.Length);
                    return(true);
                }
            }

            return(false);
        }
        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);
        }