SARIF SDK及其错误

图片2

今天,我们要检查另一个高质量的Microsoft项目,我们将英勇地钻研该项目,以发现PVS-Studio中的错误。 SARIF,是静态分析交换格式的缩写,它是一种标准(文件格式),旨在与其他工具进行交互并共享静态分析器的结果,这些工具包括:IDE,复杂的代码验证和分析工具(例如SonarQube),持续集成系统,等 SARIF SDK分别包含.NET开发人员工具以支持SARIF以及其他文件。

SARIF起源于Microsoft,现在是OASIS(处理开放标准的非营利性联盟)开发的标准。 SARIF不仅要传递分析器的结果,而且还要传递有关该工具的元数据,以及有关其启动方式,时间标签等的数据。 有关更多信息,请访问OASIS网站。 可以从GiHub上的存储库下载SARIF SDK的源代码。 通过链接可访问该项目的主页。

关于项目


SARIF SDK项目原来很小:799个.cs文件(大约98,000个非空代码行)。 该项目包含我总是从检查中排除的测试。 因此,我们感兴趣的部分代码是642个.cs文件(大约79,000个非空代码行)。 当然还不够。 从好的方面来说,从那时到之后,检查和分析都很简单快捷,我一开始就试图在图片上反映出来。 尽管如此,我还是设法找到了一些不可思议的案件。 让我们看看它们。

失误


V3070 [CWE-457]初始化“默认”变量时,使用未初始化的变量“二进制”。 MimeType.cs 90

public static class MimeType { .... /// <summary>The MIME type to use when no better MIME type is known.</summary> public static readonly string Default = Binary; .... /// <summary>The MIME type for binaries.</summary> public static readonly string Binary = "application/octet-stream"; .... } 

该字段由另一个尚未接收到值的字段初始化。 结果, 默认情况下, 默认将为字符串类型接收null值。 由于“ 默认”字段未在任何地方使用,因此错误很可能没有引起注意。 但是事情可能会改变,然后开发人员将面临不适当的结果或程序崩溃。

V3061参数'logicalLocationToIndexMap'始终在使用前在方法主体中重写。 PrereleaseCompatibilityTransformer.cs 1963

 private static JArray ConvertLogicalLocationsDictionaryToArray( .... Dictionary<LogicalLocation, int> logicalLocationToIndexMap, ....) { .... logicalLocationToIndexMap = new Dictionary<LogicalLocation, int>(LogicalLocation.ValueComparer); .... } 

代码作者不以任何方式使用logicalLocationToIndexMap参数,而是在其中写入一个不同的值。 奇怪的是,先前的值与在调用者代码中创建的空字典完全相同:

 private static bool ApplyChangesFromTC25ThroughTC30(....) { .... Dictionary<LogicalLocation, int> logicalLocationToIndexMap = null; .... logicalLocationToIndexMap = new Dictionary<LogicalLocation, int>(LogicalLocation.ValueComparer); run["logicalLocations"] = ConvertLogicalLocationsDictionaryToArray( ...., logicalLocationToIndexMap, ....); } 

奇怪而可疑的代码。

V3008 [CWE-563]连续两次为'run.Tool'变量分配值。 也许这是一个错误。 检查行:116,114。ExportRulesMetadataCommandBase.cs 116

 public partial class Run { .... public Tool Tool { get; set; } .... } public partial class Tool : .... { .... public Tool() { } .... } private void OutputSarifRulesMetada(....) { .... var run = new Run(); run.Tool = new Tool(); run.Tool = Tool.CreateFromAssemblyData(....); // <= .... } 

run.Tool属性分配了两次值。 在创建Tool对象和在Tool属性中写入值时,都不需要其他工作。 因此,重新分配闻起来很腥。

V3042 [CWE-476]可能为NullReferenceException。 '?。' 和“。” 运算符用于访问“ loc”对象的成员WhereComparer.cs 152

 private static Uri ArtifactUri(ArtifactLocation loc, Run run) { return loc?.Uri ?? loc.Resolve(run)?.Uri; } 

如果loc变量的值为null ,则将尝试从??的右侧返回该值。 运算符,导致通过空引用进行访问。

V3042 [CWE-476]可能为NullReferenceException。 '?。' 和“。” 运算符用于访问'formatString'对象的成员InsertOptionalDataVisitor.cs 194

 public override Message VisitMessage(Message node) { .... node.Text = node.Arguments?.Count > 0 ? string.Format(...., formatString.Text, ....) : formatString?.Text; .... } 

开发人员通过在条件?:运算符的两个并行分支中可能为空的formatString引用来使用不安全和安全的访问选项。

V3042 [CWE-476]可能为NullReferenceException。 '?。' 和“。” 运算符用于访问“ messageText”对象FortifyFprConverter.cs 1210的成员

V3042 [CWE-476]可能为NullReferenceException。 '?。' 和“。” 运算符用于访问“ messageText”对象FortifyFprConverter.cs 1216的成员

 private void AddMessagesToResult(Result result) { .... string messageText = (rule.ShortDescription ?? rule.FullDescription)?.Text; .... if (....) { // Replace the token with an embedded hyperlink. messageText = messageText.Replace(....); } else { // Replace the token with plain text. messageText = messageText.Replace(....); } .... } 

在此,分析器已经发出了两个有关通过null messageText引用进行可能访问的警告。 它看起来没有威胁,但仍然是一个错误。

V3080 [CWE-476]可能的空解除引用。 考虑检查“ fileDataVersionOne.Uri”。 SarifCurrentToVersionOneVisitor.cs 1030

 private IDictionary<string, FileDataVersionOne> CreateFileDataVersionOneDictionary() { .... FileDataVersionOne fileDataVersionOne = CreateFileDataVersionOne(v2File); if (fileDataVersionOne.Uri.OriginalString.Equals(key)) { .... } .... } 

分析器怀疑使用fileDataVersionOne.Uri引用时可能会出现NullReferenceException 。 让我们看看该变量来自何处,并找出分析仪是否正确。 为此,让我们仔细看一下CreateFileDataVersionOne方法的主体:

 private FileDataVersionOne CreateFileDataVersionOne(Artifact v2FileData) { FileDataVersionOne fileData = null; if (v2FileData != null) { .... fileData = new FileDataVersionOne { .... Uri = v2FileData.Location?.Uri, .... }; .... } return fileData; } public partial class FileDataVersionOne { .... public Uri Uri { get; set; } .... } 

确实,当创建FileDataVersionOne类的对象时, Uri属性可能会收到null值。 这是数据流分析和过程间分析机制协同工作的一个很好的例子。

V3080 [CWE-476]可能的空解除引用。 考虑检查“ _jsonTextWriter”。 SarifLogger.cs 242

 public virtual void Dispose() { .... if (_closeWriterOnDispose) { if (_textWriter != null) { _textWriter.Dispose(); } if (_jsonTextWriter == null) { _jsonTextWriter.Close(); } // <= } .... } 

这个片段中有一个错字。 显然_jsonTextWriter!= Null必须处于第二个块的条件下。 由于_jsonTextWriternonnull ,因此这段代码很可能因为没有崩溃而处于危险之中 。 此外,流保持开放。

V3083 [CWE-367]事件'RuleRead'的不安全调用,可能会发生NullReferenceException。 请考虑在调用事件之前将事件分配给局部变量。 FxCopConverter.cs 897

 private void ReadRule(....) { .... if (RuleRead != null) { RuleRead(....); } .... } 

事件处理不安全。 这是一个不重要的错误,可以通过例如Visual Studio技巧轻松修复。 这是IDE建议的替代品:

 private void ReadRule(....) { .... RuleRead?.Invoke(....); .... } 

只需几秒钟即可修复它,但分析器将不再抱怨它,并且IDE不会突出显示该代码。 另一个类似的错误。

  • V3083 [CWE-367]事件'ResultRead'的不安全调用,可能会发生NullReferenceException。 请考虑在调用事件之前将事件分配给局部变量。 FxCopConverter.cs 813

V3095 [CWE-476]在验证是否为null之前使用了“ v1Location”对象。 检查行:333、335。SarifVersionOneToCurrentVisitor.cs 333

 internal Location CreateLocation(LocationVersionOne v1Location) { .... string key = v1Location.LogicalLocationKey ?? v1Location.FullyQualifiedLogicalName; if (v1Location != null) { .... } .... } 

作者认为v1Location引用可能为null,并添加了适当的检查。 鉴于以上内容,我们可以看到此引用未经任何检查即可处理。 细心的重构? 好吧,你永远不会知道。

V3125 [CWE-476]在针对null验证了'v1StackFrame'对象之后,使用了该对象。 检查行:1182,1171。SarifVersionOneToCurrentVisitor.cs 1182

 internal StackFrame CreateStackFrame(StackFrameVersionOne v1StackFrame) { StackFrame stackFrame = null; if (v1StackFrame != null) { stackFrame = new StackFrame { .... }; } stackFrame.Location = CreateLocation(v1StackFrame.FullyQualifiedLogicalName, v1StackFrame.LogicalLocationKey, ....); return stackFrame; } 

与往常一样,情况相反。 首先,检查v1StackFrame引用是否为null ,然后将检查误入歧途。 但是这种情况有一个重要的警告: v1StackFramestackFrame变量在逻辑上是相关的。 请参见,如果v1StackFramenull ,则不会创建StackFrame对象,而stackFrame将保持为null。 随后由于调用stackFrame.Location而导致程序崩溃,因为此处没有检查。 因此,甚至不会出现分析器指出的危险的v1StackFrame使用情况。 仅当您将nonnull v1StackFrame值传递给CreateStackFrame方法时,此代码才有效。 我怀疑调用者代码以某种方式控制了它。 CreateStackFrame调用看起来像这样:

 Frames = v1Stack.Frames?.Select(CreateStackFrame).ToList() 

CreateStackFrame用作选择器。 此处不检查传递的引用是否为null 。 也许,在填充Frames集合时,它(写空引用)受到控制,但是我并没有深入研究。 结论已经很明显了-该代码需要作者的注意。

结论


如您所见,这篇文章并不长,但是我希望您喜欢这篇简短的文章:)以防万一,您可以随时下载我们的分析器来搜索您自己或某人的项目中的错误。

最后,一个小公告:我的下一篇文章将介绍我和我的同事在2019年的项目中发现的最有趣的错误。 再见!

要了解有关新博客文章的更多信息,欢迎您订阅以下频道:

Source: https://habr.com/ru/post/zh-CN479480/


All Articles