From aa1f8674f59fed9379a9d7300101c4fb0f4062c6 Mon Sep 17 00:00:00 2001 From: Eddy Nakamura Date: Tue, 27 Jul 2021 18:57:28 -0300 Subject: [PATCH] Adding compiler data to AppInsights (#404) * Adding compiler data to AppInsights Adding improvements * Adding basic constructor tests, adding parameters * Adding flush method, adding placeholder for dialect * Using envvariable to retrieve AppInsightsKey * Simplifying tests * Addressing PR feedback - 1 * Addressing PR feedback - 2 * Adressing PR feedback - 3 (making target relative) * Adding CompilerData struct to prevent magic strings * Removing data manipulation. let's use the raw data. * Retrieving FileVersion * Updating rules, adding more relevant information * Deleting test * removing redundant code --- src/BinSkim.Driver/AnalyzeCommand.cs | 33 +++- .../BA4002.ReportDwarfCompilerData.cs | 29 ++- .../PERules/BA4001.ReportPECompilerData.cs | 59 +++--- src/BinSkim.Sdk/BinSkim.Sdk.csproj | 1 + src/BinSkim.Sdk/BinaryAnalyzerContext.cs | 3 + src/BinSkim.Sdk/BinaryTargetManager.cs | 2 +- src/BinSkim.Sdk/CompilerData.cs | 22 +++ src/BinSkim.Sdk/CompilerDataLogger.cs | 168 ++++++++++++++++++ src/BinaryParsers/MachOBinary/MachOBinary.cs | 8 +- .../MachOBinary/SingleMachOBinary.cs | 27 +-- 10 files changed, 284 insertions(+), 68 deletions(-) create mode 100644 src/BinSkim.Sdk/CompilerData.cs create mode 100644 src/BinSkim.Sdk/CompilerDataLogger.cs diff --git a/src/BinSkim.Driver/AnalyzeCommand.cs b/src/BinSkim.Driver/AnalyzeCommand.cs index c53916a2b..3256282a7 100644 --- a/src/BinSkim.Driver/AnalyzeCommand.cs +++ b/src/BinSkim.Driver/AnalyzeCommand.cs @@ -43,6 +43,24 @@ protected override BinaryAnalyzerContext CreateContext(AnalyzeOptions options, I ShouldWarnVerbose = false; } + if (binaryAnalyzerContext.Policy != null) + { + bool isRule4001Enabled = (binaryAnalyzerContext.Policy.TryGetValue("BA4001.ReportPECompilerData.Options", out object rule4001) + && rule4001 is PropertiesDictionary property4001 + && property4001.TryGetValue("RuleEnabled", out object rule4001Value) + && rule4001Value.ToString() == "Error"); + bool isRule4002Enabled = (binaryAnalyzerContext.Policy.TryGetValue("BA4002.ReportDwarfCompilerData.Options", out object rule4002) + && rule4002 is PropertiesDictionary property4002 + && property4002.TryGetValue("RuleEnabled", out object rule4002Value) + && rule4002Value.ToString() == "Error"); + + if (isRule4001Enabled || isRule4002Enabled) + { + binaryAnalyzerContext.CompilerDataLogger = new CompilerDataLogger(binaryAnalyzerContext, + options.TargetFileSpecifiers); + } + } + return binaryAnalyzerContext; } @@ -66,7 +84,20 @@ public override int Run(AnalyzeOptions analyzeOptions) analyzeOptions.Kind = new List { ResultKind.Fail, ResultKind.NotApplicable, ResultKind.Pass }; } - int result = base.Run(analyzeOptions); + int result = 0; + + try + { + result = base.Run(analyzeOptions); + } + catch (Exception e) + { + Console.WriteLine(e); + } + finally + { + CompilerDataLogger.Flush(); + } // In BinSkim, no rule is ever applicable to every target type. For example, // we have checks that are only relevant to either 32-bit or 64-bit binaries. diff --git a/src/BinSkim.Rules/DwarfRules/BA4002.ReportDwarfCompilerData.cs b/src/BinSkim.Rules/DwarfRules/BA4002.ReportDwarfCompilerData.cs index 735deb96e..05df980cf 100644 --- a/src/BinSkim.Rules/DwarfRules/BA4002.ReportDwarfCompilerData.cs +++ b/src/BinSkim.Rules/DwarfRules/BA4002.ReportDwarfCompilerData.cs @@ -71,7 +71,7 @@ private void PrintCompilerData(BinaryAnalyzerContext context, string language, I this.PrintHeader = false; } - var processedRecords = new HashSet(); + var processedRecords = new HashSet(); foreach (ICompiler compiler in compilers) { @@ -82,24 +82,23 @@ private void PrintCompilerData(BinaryAnalyzerContext context, string language, I foreach (string file in files) { - string currentRecord = compiler.Compiler + "," + compiler.Version + "," + language; - - if (processedRecords.Contains(currentRecord)) + var record = new CompilerData + { + BinaryType = "ELF", + Language = language, + CompilerName = compiler.Compiler.ToString(), + CompilerBackEndVersion = compiler.Version.ToString(), + CompilerFrontEndVersion = compiler.Version.ToString(), + }; + + if (processedRecords.Contains(record)) { continue; } - processedRecords.Add(currentRecord); - - Console.Write($"{context.TargetUri.LocalPath},"); - Console.Write($"{compiler.Compiler},"); - Console.Write($"{compiler.Version},"); - Console.Write($"{compiler.Version},"); - Console.Write($"{language},"); - Console.Write($"{file},"); - Console.Write($"{file},"); - Console.Write($"{context?.Hashes?.Sha256},"); - Console.WriteLine(); + processedRecords.Add(record); + + context.CompilerDataLogger.Write(record, file); } } } diff --git a/src/BinSkim.Rules/PERules/BA4001.ReportPECompilerData.cs b/src/BinSkim.Rules/PERules/BA4001.ReportPECompilerData.cs index d67f469a0..359dbd462 100644 --- a/src/BinSkim.Rules/PERules/BA4001.ReportPECompilerData.cs +++ b/src/BinSkim.Rules/PERules/BA4001.ReportPECompilerData.cs @@ -48,24 +48,34 @@ public override void AnalyzePortableExecutableAndPdb(BinaryAnalyzerContext conte if (PrintHeader) { - Console.WriteLine("Target,Compiler Name,Compiler BackEnd Version,Compiler FrontEnd Version,Language,Module Name,Module Library,Hash,Error"); + context.CompilerDataLogger.PrintHeader(); PrintHeader = false; } if (pdb == null) { - Console.Write(context.TargetUri.LocalPath + ","); - Console.WriteLine($",,,,,,{context?.Hashes?.Sha256},{target.PdbParseException.Message}"); + string errorMessage = target.PdbParseException.Message; + context.CompilerDataLogger.WriteException(errorMessage); return; } - var records = new Dictionary(); + var records = new Dictionary(); if (target.PE.IsManaged) { - string record = $".NET Compiler,{target.PE.LinkerVersion},{target.PE.LinkerVersion},{Language.MSIL}"; - - if (!records.TryGetValue(record, out ObjectModuleDetails value)) + var record = new CompilerData + { + BinaryType = "PE", + CompilerName = ".NET Compiler", + Language = nameof(Language.MSIL), + DebuggingFileName = pdb.GlobalScope?.Name, + DebuggingFileGuid = pdb.GlobalScope?.Guid.ToString(), + FileVersion = target.PE.FileVersion?.FileVersion, + CompilerBackEndVersion = target.PE.LinkerVersion.ToString(), + CompilerFrontEndVersion = target.PE.LinkerVersion.ToString(), + }; + + if (!records.ContainsKey(record)) { records[record] = null; } @@ -77,33 +87,28 @@ public override void AnalyzePortableExecutableAndPdb(BinaryAnalyzerContext conte Symbol om = omView.Value; ObjectModuleDetails omDetails = om.GetObjectModuleDetails(); - string record = - omDetails.CompilerName?.Replace(",", "_").Trim() + "," + - omDetails.CompilerBackEndVersion + "," + - omDetails.CompilerFrontEndVersion + "," + - omDetails.Language; - - if (!records.TryGetValue(record, out ObjectModuleDetails value)) + var record = new CompilerData + { + BinaryType = "PE", + CompilerName = omDetails.CompilerName, + Language = omDetails.Language.ToString(), + DebuggingFileName = pdb.GlobalScope?.Name, + FileVersion = target.PE.FileVersion?.FileVersion, + DebuggingFileGuid = pdb.GlobalScope?.Guid.ToString(), + CompilerBackEndVersion = omDetails.CompilerBackEndVersion.ToString(), + CompilerFrontEndVersion = omDetails.CompilerFrontEndVersion.ToString(), + }; + + if (!records.ContainsKey(record)) { records[record] = omDetails; } } } - foreach (KeyValuePair kv in records) + foreach (KeyValuePair kv in records) { - string compilerData = kv.Key; - ObjectModuleDetails omDetails = kv.Value; - - string name = omDetails.Name?.Replace(",", "_"); - string library = omDetails.Library?.Replace(",", ";"); - - Console.Write($"{context.TargetUri.LocalPath},"); - Console.Write($"{compilerData},"); - Console.Write($"{name},"); - Console.Write($"{(name == library ? string.Empty : library)},"); - Console.Write($"{context?.Hashes?.Sha256},"); - Console.WriteLine(); + context.CompilerDataLogger.Write(kv.Key, kv.Value); } } } diff --git a/src/BinSkim.Sdk/BinSkim.Sdk.csproj b/src/BinSkim.Sdk/BinSkim.Sdk.csproj index d661c836d..4d1f538c5 100644 --- a/src/BinSkim.Sdk/BinSkim.Sdk.csproj +++ b/src/BinSkim.Sdk/BinSkim.Sdk.csproj @@ -9,6 +9,7 @@ + diff --git a/src/BinSkim.Sdk/BinaryAnalyzerContext.cs b/src/BinSkim.Sdk/BinaryAnalyzerContext.cs index 5ee7fadc2..a82e6877c 100644 --- a/src/BinSkim.Sdk/BinaryAnalyzerContext.cs +++ b/src/BinSkim.Sdk/BinaryAnalyzerContext.cs @@ -55,6 +55,7 @@ public Uri TargetUri this.uri = value; } } + public bool TracePdbLoads { get; set; } public string SymbolPath { get; set; } @@ -77,6 +78,8 @@ public string MimeType public bool AnalysisComplete { get; set; } public DefaultTraces Traces { get; set; } + public CompilerDataLogger CompilerDataLogger { get; set; } + private bool disposed = false; protected virtual void Dispose(bool disposing) diff --git a/src/BinSkim.Sdk/BinaryTargetManager.cs b/src/BinSkim.Sdk/BinaryTargetManager.cs index 9c8d72922..ec7ea48ec 100644 --- a/src/BinSkim.Sdk/BinaryTargetManager.cs +++ b/src/BinSkim.Sdk/BinaryTargetManager.cs @@ -7,7 +7,7 @@ namespace Microsoft.CodeAnalysis.IL.Sdk { - internal class BinaryTargetManager + internal static class BinaryTargetManager { // We may want to consider changing this to an extension/plugin model rather than a hardcoded list of supported binary parsers. // However, for now this will do. diff --git a/src/BinSkim.Sdk/CompilerData.cs b/src/BinSkim.Sdk/CompilerData.cs new file mode 100644 index 000000000..beca37e43 --- /dev/null +++ b/src/BinSkim.Sdk/CompilerData.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.CodeAnalysis.IL.Sdk +{ + public struct CompilerData + { + public string Language { get; set; } + public string BinaryType { get; set; } + public string FileVersion { get; set; } + public string CompilerName { get; set; } + public string DebuggingFileName { get; set; } + public string DebuggingFileGuid { get; set; } + public string CompilerBackEndVersion { get; set; } + public string CompilerFrontEndVersion { get; set; } + + public override string ToString() + { + return $"{CompilerName},{CompilerBackEndVersion},{CompilerFrontEndVersion},{FileVersion},{BinaryType},{Language},{DebuggingFileName},{DebuggingFileGuid}"; + } + } +} diff --git a/src/BinSkim.Sdk/CompilerDataLogger.cs b/src/BinSkim.Sdk/CompilerDataLogger.cs new file mode 100644 index 000000000..12f892550 --- /dev/null +++ b/src/BinSkim.Sdk/CompilerDataLogger.cs @@ -0,0 +1,168 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Security; + +using Microsoft.ApplicationInsights; +using Microsoft.ApplicationInsights.Extensibility; +using Microsoft.CodeAnalysis.BinaryParsers.ProgramDatabase; +using Microsoft.CodeAnalysis.Sarif; + +namespace Microsoft.CodeAnalysis.IL.Sdk +{ + public class CompilerDataLogger + { + private const string CompilerEventName = "CompilerInformation"; + + private readonly bool appInsightsRegistered; + + private readonly string sha256; + private readonly string relativeFilePath; + + private static TelemetryClient s_telemetryClient; + private static TelemetryConfiguration s_telemetryConfiguration; + + public CompilerDataLogger(IAnalysisContext analysisContext, + IEnumerable targetFileSpecifiers) + { + try + { + string appInsightsKey = Environment.GetEnvironmentVariable("BinskimAppInsightsKey"); + if (!string.IsNullOrEmpty(appInsightsKey) && Guid.TryParse(appInsightsKey, out _)) + { + Initialize(appInsightsKey); + this.appInsightsRegistered = true; + } + } + catch (SecurityException) + { + // User does not have access to retrieve information from environment variables. + } + + this.sha256 = analysisContext?.Hashes?.Sha256 ?? string.Empty; + this.relativeFilePath = analysisContext?.TargetUri?.LocalPath ?? string.Empty; + + foreach (string path in targetFileSpecifiers) + { + // We must get directory name because there are cases where the targetFilePath is + // c:\path\*.dll + string directoryName = Path.GetDirectoryName(path); + this.relativeFilePath = this.relativeFilePath.Replace(directoryName, string.Empty); + } + } + + public static void Initialize(string instrumentationKey) + { + if (s_telemetryConfiguration == null && s_telemetryClient == null) + { + s_telemetryConfiguration = new TelemetryConfiguration(instrumentationKey); + s_telemetryClient = new TelemetryClient(s_telemetryConfiguration); + } + } + + public static void Flush() + { + s_telemetryClient?.Flush(); + } + + public void PrintHeader() + { + if (!this.appInsightsRegistered) + { + Console.WriteLine("Target,Compiler Name,Compiler BackEnd Version,Compiler FrontEnd Version,File Version,Binary Type,Language,Debugging FileName, Debugging FileGuid,Dialect,Module Name,Module Library,Hash,Error"); + } + } + + public void Write(CompilerData compilerData, ObjectModuleDetails omDetails) + { + string name = omDetails?.Name; + string library = omDetails?.Library; + if (this.appInsightsRegistered) + { + s_telemetryClient.TrackEvent(CompilerEventName, properties: new Dictionary + { + { "target", this.relativeFilePath }, + { "compilerName", compilerData.CompilerName }, + { "compilerBackEndVersion", compilerData.CompilerBackEndVersion }, + { "compilerFrontEndVersion", compilerData.CompilerFrontEndVersion }, + { "fileVersion", compilerData.FileVersion ?? string.Empty }, + { "binaryType", compilerData.BinaryType }, + { "language", compilerData.Language }, + { "debuggingFileName", compilerData.DebuggingFileName }, + { "debuggingGuid", compilerData.DebuggingFileGuid }, + { "dialect", string.Empty }, + { "moduleName", name ?? string.Empty }, + { "moduleLibrary", (name == library ? string.Empty : library) }, + { "hash", this.sha256 }, + { "error", string.Empty } + }); + } + else + { + string log = $@"{this.relativeFilePath},{compilerData},,""{name}"",""{(name == library ? string.Empty : library)}"",{this.sha256},"; + Console.WriteLine(log); + } + } + + public void Write(CompilerData compilerData, string file) + { + if (this.appInsightsRegistered) + { + s_telemetryClient.TrackEvent(CompilerEventName, properties: new Dictionary + { + { "target", this.relativeFilePath }, + { "compilerName", compilerData.CompilerName }, + { "compilerBackEndVersion", compilerData.CompilerBackEndVersion }, + { "compilerFrontEndVersion", compilerData.CompilerFrontEndVersion }, + { "fileVersion", string.Empty }, + { "binaryType", compilerData.BinaryType }, + { "language", compilerData.Language }, + { "debuggingFileName", compilerData.DebuggingFileName ?? string.Empty }, + { "debuggingGuid", compilerData.DebuggingFileGuid ?? string.Empty }, + { "dialect", string.Empty }, + { "moduleName", file ?? string.Empty }, + { "moduleLibrary", string.Empty }, + { "hash", this.sha256 }, + { "error", string.Empty } + }); + } + else + { + string log = $"{this.relativeFilePath},{compilerData},,{file},,{this.sha256},"; + Console.WriteLine(log); + } + } + + public void WriteException(string errorMessage) + { + if (this.appInsightsRegistered) + { + s_telemetryClient.TrackEvent(CompilerEventName, properties: new Dictionary + { + { "target", this.relativeFilePath }, + { "compilerName", string.Empty }, + { "compilerBackEndVersion", string.Empty }, + { "compilerFrontEndVersion", string.Empty }, + { "fileVersion", string.Empty }, + { "binaryType", string.Empty }, + { "language", string.Empty }, + { "debuggingFileName", string.Empty }, + { "debuggingGuid", string.Empty }, + { "dialect", string.Empty }, + { "moduleName", string.Empty }, + { "moduleLibrary", string.Empty }, + { "hash", this.sha256 }, + { "error", errorMessage } + }); + } + else + { + string log = $"{this.relativeFilePath},,,,,,,,,,,,{this.sha256},{errorMessage}"; + Console.WriteLine(log); + } + } + } +} diff --git a/src/BinaryParsers/MachOBinary/MachOBinary.cs b/src/BinaryParsers/MachOBinary/MachOBinary.cs index 20cd6fef6..9c8b532e6 100644 --- a/src/BinaryParsers/MachOBinary/MachOBinary.cs +++ b/src/BinaryParsers/MachOBinary/MachOBinary.cs @@ -52,7 +52,7 @@ public static bool CanLoadBinary(Uri uri) catch (UnauthorizedAccessException) { return false; } } - public bool IsFatMachO => this.MachOs != null && this.MachOs.Count > 1; + public bool IsFatMachO => this.MachOs?.Count > 1; public IReadOnlyList MachOs { get; } @@ -66,9 +66,9 @@ public static bool CanLoadBinary(Uri uri) public int DwarfVersion { get; set; } = -1; /// - /// Unit type of Dwarf used.. + /// Unit type of Dwarf used. /// - public DwarfUnitType DwarfUnitType { get; set; } = DwarfUnitType.Unknown; + public DwarfUnitType DwarfUnitType { get; set; } /// /// Gets address offset within module when it is loaded. @@ -115,6 +115,6 @@ public DwarfLanguage GetLanguage() public ICompiler[] Compilers => throw new NotImplementedException(); - #endregion + #endregion IDwarfBinary interface } } diff --git a/src/BinaryParsers/MachOBinary/SingleMachOBinary.cs b/src/BinaryParsers/MachOBinary/SingleMachOBinary.cs index 8a8ab0e77..26bf0e6a6 100644 --- a/src/BinaryParsers/MachOBinary/SingleMachOBinary.cs +++ b/src/BinaryParsers/MachOBinary/SingleMachOBinary.cs @@ -13,19 +13,19 @@ namespace Microsoft.CodeAnalysis.BinaryParsers { public class SingleMachOBinary : BinaryBase, IDwarfBinary { - private const string SECTIONNAME_DEBUG_INFO = "__debug_info"; - private const string SECTIONNAME_DEBUG_ABBREV = "__debug_abbrev"; + private const string SECTIONNAME_TEXT = "__text"; + private const string SECTIONNAME_DATA = "__data"; + private const string SECTIONNAME_EH_FRAME = "__eh_frame"; private const string SECTIONNAME_DEBUG_STR = "__debug_str"; private const string SECTIONNAME_DEBUG_LINE = "__debug_line"; + private const string SECTIONNAME_DEBUG_INFO = "__debug_info"; private const string SECTIONNAME_DEBUG_FRAME = "__debug_frame"; - private const string SECTIONNAME_EH_FRAME = "__eh_frame"; - private const string SECTIONNAME_TEXT = "__text"; - private const string SECTIONNAME_DATA = "__data"; + private const string SECTIONNAME_DEBUG_ABBREV = "__debug_abbrev"; public SingleMachOBinary(MachO singleMachO, Uri uri) : base(uri) { this.MachO = singleMachO; - LoadCompilationUnits(); + CompilationUnits = LoadCompilationUnits(); } public MachO MachO { get; } @@ -44,20 +44,7 @@ public SingleMachOBinary(MachO singleMachO, Uri uri) : base(uri) public IEnumerable EntryPoint => this.MachO.GetCommandsOfType(); - private List compilationUnits; - - public List CompilationUnits - { - get - { - if (compilationUnits == null) - { - compilationUnits = LoadCompilationUnits(); - } - - return compilationUnits; - } - } + public List CompilationUnits { get; } private List lineNumberPrograms;