예제 #1
0
        private static object[] BuildDefaultTestCase(FailureLevel level, ResultKind kind = ResultKind.Fail)
        {
            string lineLabel = level != FailureLevel.None
                ? level.ToString().ToLowerInvariant()
                : kind.ToString().ToLowerInvariant();

            if (kind == ResultKind.Informational)
            {
                // Console reporting historically abbreviates this term
                lineLabel = "info";
            }

            if (level == FailureLevel.None && kind == ResultKind.None)
            {
                // No good information? Mark it as informational.
                lineLabel = "info";
            }

            return(new object[]
            {
                level,
                kind,
                MultiLineTestRegion,
                $"{TestData.TestAnalysisTarget}(2,4,3,5): {lineLabel} {TestData.TestRuleId}: First: 42, Second: 54",
                TestData.TestAnalysisTarget
            });
        }
예제 #2
0
        private static string GetMessageText(
            string toolName,
            Uri uri,
            Region region,
            string ruleId,
            string message,
            ResultKind kind,
            FailureLevel level)
        {
            string path = ConstructPathFromUri(uri);

            string issueType = null;

            switch (level)
            {
            case FailureLevel.Note:
                issueType = "note";
                break;

            case FailureLevel.Error:
                issueType = "error";
                break;

            case FailureLevel.Warning:
                issueType = "warning";
                break;

            case FailureLevel.None:
                issueType = kind.ToString().ToLowerInvariant();
                // Shorten to 'info' for compatibility with previous behavior.
                if (issueType == "informational")
                {
                    issueType = "info";
                }
                break;

            default:
                throw new InvalidOperationException("Unknown message level:" + level.ToString());
            }

            string detailedDiagnosis = NormalizeMessage(message, enquote: false);

            string location = "";

            if (region != null)
            {
                // TODO: FormatForVisualStudio doesn't handle
                // binary and char offsets only.
                location = region.FormatForVisualStudio();
            }

            return((path != null ? (path + location) : toolName)
                   + $": {issueType} "
                   + (!string.IsNullOrEmpty(ruleId) ? (ruleId + ": ") : "")
                   + detailedDiagnosis);
        }
예제 #3
0
        public void SarifWorkItemExtensions_CreateWorkItemTitle_HandlesSingleResultWithRuleIdOnly()
        {
            var sb = new StringBuilder();

            foreach (Tuple <string, Result> tuple in ResultsWithVariousRuleExpressions)
            {
                Result   result   = tuple.Item2;
                SarifLog sarifLog = CreateLogWithEmptyRun();

                Run run = sarifLog.Runs[0];
                run.Results.Add(tuple.Item2);

                string title  = sarifLog.Runs[0].CreateWorkItemTitle(false);
                string ruleId = result.ResolvedRuleId(run);

                if (!title.Contains(ToolName))
                {
                    sb.AppendLine($"[Test case '{tuple.Item1}']: Title did not contain generated tool name.");
                }

                if (!title.Contains(ruleId))
                {
                    sb.AppendLine($"[Test case '{tuple.Item1}']: Title did not contain expected rule id '{ruleId}'.");
                }

                FailureLevel level = result.Level;
                if (!title.Contains(level.ToString()))
                {
                    sb.AppendLine($"[Test case '{tuple.Item1}']: Title did not contain expected failure level '{level}'.");
                }

                string location = result.Locations?[0].PhysicalLocation?.ArtifactLocation?.Uri?.OriginalString;
                if (!string.IsNullOrEmpty(location) && !title.Contains(location))
                {
                    sb.AppendLine($"[Test case '{tuple.Item1}']: Title did not contain expected location '{location}'.");
                }
            }

            sb.Length.Should().Be(0, because: Environment.NewLine + sb.ToString());
        }
예제 #4
0
        private static string GetMessageText(
            Uri uri,
            Region region,
            string ruleId,
            string message,
            ResultKind kind,
            FailureLevel level)
        {
            string path = null;

            ValidateKindAndLevel(kind, level);

            if (uri != null)
            {
                // If a path refers to a URI of form file://blah, we will convert to the local path
                if (uri.IsAbsoluteUri && uri.Scheme == Uri.UriSchemeFile)
                {
                    path = uri.LocalPath;
                }
                else
                {
                    path = uri.ToString();
                }
            }

            string issueType = null;

            switch (level)
            {
            case FailureLevel.Note:
                issueType = "info";
                break;

            case FailureLevel.Error:
                issueType = "error";
                break;

            case FailureLevel.Warning:
                issueType = "warning";
                break;

            case FailureLevel.None:
                issueType = kind.ToString().ToLowerInvariant();
                break;


            default:
                throw new InvalidOperationException("Unknown message kind:" + level.ToString());
            }

            string detailedDiagnosis = NormalizeMessage(message, enquote: false);

            string location = "";

            if (region != null)
            {
                // TODO
                if (region.CharOffset > 0 ||
                    region.ByteOffset > 0 ||
                    region.StartColumn == 0)
                {
                    return(string.Empty);
                }

                if (region.StartLine == 0)
                {
                    throw new InvalidOperationException();
                }

                location = region.FormatForVisualStudio();
            }

            string result = (path != null ? (path + location + ": ") : "") +
                            issueType + (!string.IsNullOrEmpty(ruleId) ? " " : "") +
                            (!string.IsNullOrEmpty(ruleId) ? (ruleId + ": ") : "") +
                            detailedDiagnosis;

            return(result);
        }
예제 #5
0
        private static string GetMessageText(
            string toolName,
            Uri uri,
            Region region,
            string ruleId,
            string message,
            ResultKind kind,
            FailureLevel level)
        {
            string path = null;

            ValidateKindAndLevel(kind, level);

            path = ConstructPathFromUri(uri);

            string issueType = null;

            switch (level)
            {
            case FailureLevel.Note:
                issueType = "note";
                break;

            case FailureLevel.Error:
                issueType = "error";
                break;

            case FailureLevel.Warning:
                issueType = "warning";
                break;

            case FailureLevel.None:
                issueType = kind.ToString().ToLowerInvariant();
                // Shorten to 'info' for compat reasons with previous behaviors.
                if (issueType == "informational")
                {
                    issueType = "info";
                }
                ;
                break;


            default:
                throw new InvalidOperationException("Unknown message level:" + level.ToString());
            }

            string detailedDiagnosis = NormalizeMessage(message, enquote: false);

            string location = "";

            if (region != null)
            {
                // TODO
                if (region.CharOffset > 0 ||
                    region.ByteOffset > 0 ||
                    region.StartColumn == 0)
                {
                    return(string.Empty);
                }

                if (region.StartLine == 0)
                {
                    throw new InvalidOperationException();
                }

                location = region.FormatForVisualStudio();
            }

            string messageText =
                (path != null ? (path + location) : toolName) + ": " +
                issueType + (!string.IsNullOrEmpty(ruleId) ? " " : "") +
                (!string.IsNullOrEmpty(ruleId) ? (ruleId + ": ") : "") +
                detailedDiagnosis;

            return(messageText);
        }
        private void RunMatchExpressionForFileNameRegex(AnalyzeContext context, MatchExpression matchExpression, FailureLevel level)
        {
            ReportingDescriptor reportingDescriptor = this;

            string levelText   = level.ToString();
            string fingerprint = null;
            IDictionary <string, string> groups = new Dictionary <string, string>();

            string filePath = context.TargetUri.LocalPath;
            string fingerprintText = null, validatorMessage = null;
            string validationPrefix = string.Empty, validationSuffix = string.Empty;

            if (_validators != null && matchExpression.IsValidatorEnabled)
            {
                Validation state = _validators.Validate(reportingDescriptor.Name,
                                                        context.DynamicValidation,
                                                        ref filePath,
                                                        ref groups,
                                                        ref levelText,
                                                        ref fingerprintText,
                                                        ref validatorMessage,
                                                        out bool pluginSupportsDynamicValidation);

                if (!Enum.TryParse <FailureLevel>(levelText, out level))
                {
                    // An illegal failure level '{0}' was returned running check '{1}' against '{2}'.
                    context.Logger.LogToolNotification(
                        Errors.CreateNotification(
                            context.TargetUri,
                            "ERR998.ValidatorReturnedIllegalResultLevel",
                            context.Rule.Id,
                            FailureLevel.Error,
                            exception: null,
                            persistExceptionStack: false,
                            messageFormat: SpamResources.ERR998_ValidatorReturnedIllegalResultLevel,
                            levelText,
                            context.Rule.Id,
                            context.TargetUri.GetFileName()));

                    // If we don't understand the failure level, elevate it to error.
                    level = FailureLevel.Error;
                }

                switch (state)
                {
                case Validation.NoMatch:
                {
                    // The validator determined the match is a false positive.
                    // i.e., it is not the kind of artifact we're looking for.
                    // We should suspend processing and move to the next match.
                    return;
                }

                case Validation.None:
                case Validation.ValidatorReturnedIllegalValidationState:
                {
                    // An illegal state was returned running check '{0}' against '{1}' ({2}).
                    context.Logger.LogToolNotification(
                        Errors.CreateNotification(
                            context.TargetUri,
                            "ERR998.ValidatorReturnedIllegalValidationState",
                            context.Rule.Id,
                            FailureLevel.Error,
                            exception: null,
                            persistExceptionStack: false,
                            messageFormat: SpamResources.ERR998_ValidatorReturnedIllegalValidationState,
                            context.Rule.Id,
                            context.TargetUri.GetFileName(),
                            validatorMessage));

                    level = FailureLevel.Error;
                    return;
                }

                case Validation.Authorized:
                {
                    level = FailureLevel.Error;

                    // Contributes to building a message fragment such as:
                    // 'SomeFile.txt' is an exposed SomeSecret file [...].
                    validationPrefix = "an exposed ";
                    break;
                }

                case Validation.Expired:
                {
                    level = FailureLevel.Warning;

                    // Contributes to building a message fragment such as:
                    // 'SomeFile.txt' contains an expired SomeApi token[...].
                    validationPrefix = "an expired ";
                    break;
                }

                case Validation.PasswordProtected:
                {
                    level = FailureLevel.Warning;

                    // Contributes to building a message fragment such as:
                    // 'SomeFile.txt' contains a password-protected SomeSecret file
                    // which could be exfiltrated and potentially brute-forced offline.
                    validationPrefix = "a password-protected ";
                    validationSuffix = " which could be exfiltrated and potentially brute-forced offline";
                    break;
                }

                case Validation.UnknownHost:
                case Validation.Unauthorized:
                case Validation.InvalidForConsultedAuthorities:
                {
                    throw new InvalidOperationException();
                }

                case Validation.Unknown:
                {
                    level = FailureLevel.Warning;

                    validationPrefix = "an apparent ";
                    if (!context.DynamicValidation)
                    {
                        if (pluginSupportsDynamicValidation)
                        {
                            // This indicates that dynamic validation was disabled but we
                            // passed this result to a validator that could have performed
                            // this work.
                            validationSuffix = ". No validation occurred as it was not enabled. Pass '--dynamic-validation' on the command-line to validate this match";
                        }
                        else
                        {
                            // No validation was requested. The plugin indicated
                            // that is can't perform this work in any case.
                            validationSuffix = string.Empty;
                        }
                    }
                    else if (pluginSupportsDynamicValidation)
                    {
                        validationSuffix = ", the validity of which could not be determined by runtime analysis";
                    }
                    else
                    {
                        // Validation was requested. But the plugin indicated
                        // that it can't perform this work in any case.
                        validationSuffix = string.Empty;
                    }

                    break;
                }

                case Validation.ValidatorNotFound:
                {
                    // TODO: should we have an explicit indicator in
                    // all cases that tells us whether this is an
                    // expected condition or not?
                    validationPrefix = "an apparent ";

                    break;
                }

                default:
                {
                    throw new InvalidOperationException($"Unrecognized validation value '{state}'.");
                }
                }
            }

            Dictionary <string, string> messageArguments =
                matchExpression.MessageArguments != null ?
                new Dictionary <string, string>(matchExpression.MessageArguments) :
                new Dictionary <string, string>();

            messageArguments["validationPrefix"] = validationPrefix;
            messageArguments["validationSuffix"] = validationSuffix;

            IList <string> arguments = GetMessageArguments(
                match: null,
                matchExpression.ArgumentNameToIndexMap,
                context.TargetUri.LocalPath,
                validatorMessage: NormalizeValidatorMessage(validatorMessage),
                messageArguments);

            Result result = this.ConstructResult(
                context.TargetUri,
                reportingDescriptor.Id,
                level,
                region: null,
                flexMatch: null,
                fingerprint,
                matchExpression,
                arguments);

            context.Logger.Log(reportingDescriptor, result);
        }
        private void RunMatchExpressionForContentsRegex(
            FlexMatch binary64DecodedMatch,
            AnalyzeContext context,
            MatchExpression matchExpression,
            FailureLevel level)
        {
            string filePath   = context.TargetUri.GetFilePath();
            string searchText = binary64DecodedMatch != null
                                                   ? Decode(binary64DecodedMatch.Value)
                                                   : context.FileContents;

            foreach (FlexMatch flexMatch in _engine.Matches(searchText, matchExpression.ContentsRegex))
            {
                if (!flexMatch.Success)
                {
                    continue;
                }

                ReportingDescriptor reportingDescriptor = this;

                Regex regex =
                    CachedDotNetRegex.GetOrCreateRegex(matchExpression.ContentsRegex,
                                                       RegexDefaults.DefaultOptionsCaseInsensitive);

                Match match = regex.Match(flexMatch.Value);

                string refinedMatchedPattern = match.Groups["refine"].Value;

                IDictionary <string, string> groups = match.Groups.CopyToDictionary(regex.GetGroupNames());

                Debug.Assert(!groups.ContainsKey("scanTargetFullPath"));
                groups["scanTargetFullPath"] = filePath;

                if (matchExpression.Properties != null)
                {
                    foreach (KeyValuePair <string, string> kv in matchExpression.Properties)
                    {
                        // We will never allow a group returned by a dynamically executing
                        // regex to overwrite a static value in the match expression. This
                        // allows the match expression to provide a default value that
                        // may be replaced by the analysis.
                        if (!groups.ContainsKey(kv.Key))
                        {
                            groups[kv.Key] = kv.Value;
                        }
                    }
                }

                if (string.IsNullOrEmpty(refinedMatchedPattern))
                {
                    refinedMatchedPattern = flexMatch.Value;
                }

                string levelText = level.ToString();

                Validation state            = 0;
                string     fingerprint      = null;
                string     validatorMessage = null;
                string     validationPrefix = string.Empty;
                string     validationSuffix = string.Empty;

                if (_validators != null && matchExpression.IsValidatorEnabled)
                {
                    state = _validators.Validate(reportingDescriptor.Name,
                                                 context.DynamicValidation,
                                                 ref refinedMatchedPattern,
                                                 ref groups,
                                                 ref levelText,
                                                 ref fingerprint,
                                                 ref validatorMessage,
                                                 out bool pluginSupportsDynamicValidation);

                    if (!Enum.TryParse <FailureLevel>(levelText, out level))
                    {
                        // An illegal failure level '{0}' was returned running check '{1}' against '{2}'.
                        context.Logger.LogToolNotification(
                            Errors.CreateNotification(
                                context.TargetUri,
                                "ERR998.ValidatorReturnedIllegalResultLevel",
                                context.Rule.Id,
                                FailureLevel.Error,
                                exception: null,
                                persistExceptionStack: false,
                                messageFormat: SpamResources.ERR998_ValidatorReturnedIllegalResultLevel,
                                levelText,
                                context.Rule.Id,
                                context.TargetUri.GetFileName()));

                        // If we don't understand the failure level, elevate it to error.
                        level = FailureLevel.Error;
                    }

                    SetPropertiesBasedOnValidationState(state,
                                                        context,
                                                        ref level,
                                                        ref validationPrefix,
                                                        ref validationSuffix,
                                                        ref validatorMessage,
                                                        pluginSupportsDynamicValidation);

                    if (state == Validation.None ||
                        state == Validation.NoMatch ||
                        state == Validation.ValidatorReturnedIllegalValidationState)
                    {
                        continue;
                    }
                }

                // If we're matching against decoded contents, the region should
                // relate to the base64-encoded scan target content. We do use
                // the decoded content for the fingerprint, however.
                FlexMatch regionFlexMatch = binary64DecodedMatch ?? flexMatch;

                Region region = ConstructRegion(context, regionFlexMatch, refinedMatchedPattern);

                Dictionary <string, string> messageArguments = matchExpression.MessageArguments != null ?
                                                               new Dictionary <string, string>(matchExpression.MessageArguments) :
                                                               new Dictionary <string, string>();

                messageArguments["encoding"] = binary64DecodedMatch != null ?
                                               "base64-encoded" :
                                               string.Empty; // We don't bother to report a value for plaintext content

                messageArguments["validationPrefix"] = validationPrefix;
                messageArguments["validationSuffix"] = validationSuffix;

                IList <string> arguments = GetMessageArguments(match,
                                                               matchExpression.ArgumentNameToIndexMap,
                                                               filePath,
                                                               validatorMessage: NormalizeValidatorMessage(validatorMessage),
                                                               messageArguments);

                Result result = ConstructResult(context.TargetUri,
                                                reportingDescriptor.Id,
                                                level,
                                                region,
                                                flexMatch,
                                                fingerprint,
                                                matchExpression,
                                                arguments);

                // This skimmer instance mutates its reporting descriptor state,
                // for example, the sub-id may change for every match
                // expression. We will therefore generate a snapshot of
                // current ReportingDescriptor state when logging.
                context.Logger.Log(reportingDescriptor, result);
            }
        }