Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding compiler data to AppInsights #404

Merged
merged 22 commits into from
Jul 27, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d7d01f9
Adding compiler data to AppInsights
eddynaka Jul 7, 2021
9a1a8cf
Adding basic constructor tests, adding parameters
eddynaka Jul 13, 2021
ee25a1c
Adding flush method, adding placeholder for dialect
eddynaka Jul 13, 2021
aa8d558
Merge branch 'main' into users/ednakamu/application-insights-compiler…
eddynaka Jul 14, 2021
477a0e9
Merge branch 'main' into users/ednakamu/application-insights-compiler…
eddynaka Jul 14, 2021
db3aece
Using envvariable to retrieve AppInsightsKey
eddynaka Jul 15, 2021
5e6450b
Merge branch 'users/ednakamu/application-insights-compiler-data' of h…
eddynaka Jul 15, 2021
fd254b4
Simplifying tests
eddynaka Jul 15, 2021
b0f5106
Merge branch 'main' into users/ednakamu/application-insights-compiler…
eddynaka Jul 15, 2021
aedb1eb
Merge branch 'main' into users/ednakamu/application-insights-compiler…
eddynaka Jul 15, 2021
729453c
Addressing PR feedback - 1
eddynaka Jul 16, 2021
9d21968
Merge branch 'users/ednakamu/application-insights-compiler-data' of h…
eddynaka Jul 16, 2021
7ce6027
Addressing PR feedback - 2
eddynaka Jul 17, 2021
5112e4a
Adressing PR feedback - 3 (making target relative)
eddynaka Jul 17, 2021
4fb5fbc
Adding CompilerData struct to prevent magic strings
eddynaka Jul 26, 2021
a6a3632
Merge branch 'main' into users/ednakamu/application-insights-compiler…
eddynaka Jul 26, 2021
0527ae9
Removing data manipulation. let's use the raw data.
eddynaka Jul 27, 2021
843eeed
Merge branch 'users/ednakamu/application-insights-compiler-data' of h…
eddynaka Jul 27, 2021
2ddf392
Retrieving FileVersion
eddynaka Jul 27, 2021
ff40b7b
Updating rules, adding more relevant information
eddynaka Jul 27, 2021
0c7b53b
Deleting test
eddynaka Jul 27, 2021
ed8cb64
removing redundant code
eddynaka Jul 27, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/BinSkim.Driver/AnalyzeCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ protected override BinaryAnalyzerContext CreateContext(AnalyzeOptions options, I
ShouldWarnVerbose = false;
}

binaryAnalyzerContext.CompilerDataLogger = new CompilerDataLogger(binaryAnalyzerContext, options.RepositoryUri, options.PipelineName);
eddynaka marked this conversation as resolved.
Show resolved Hide resolved

return binaryAnalyzerContext;
}

Expand All @@ -68,6 +70,8 @@ public override int Run(AnalyzeOptions analyzeOptions)

int result = base.Run(analyzeOptions);

CompilerDataLogger.Flush();
eddynaka marked this conversation as resolved.
Show resolved Hide resolved

// 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.
// Because of this, the return code bit for RuleNotApplicableToTarget is not
Expand Down
10 changes: 10 additions & 0 deletions src/BinSkim.Driver/AnalyzeOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,15 @@ public class AnalyzeOptions : AnalyzeOptionsBase
HelpText = "Emit verbose output. The resulting comprehensive report is designed to provide appropriate evidence for compliance scenarios.")]
[Obsolete("Use --level and --kind instead.")]
public bool Verbose { get; set; }

[Option(
"repository-uri",
HelpText = "RepositoryUri is required to rule BA4001 and BA4002.")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RepositoryUri is required to rule BA4001 and BA4002

Whenever you show an opaque rule id to the user, you should add the readable name. Not everyone can remember what BA4001 is. Also, you've described a fact here 'this data is required to run rule BA4001.ReportPECompilerData' but you haven't described what that data is.

What is this data? Does it make sense that we require a single repo URL for a scan run??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the helptext. Let me know what do you think.

About your question...I thought about it, and I could not find anything better based on this:
https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml

my idea was to find one pre-defined variable that any pipeline would be able to get.

public string RepositoryUri { get; set; }

[Option(
"pipeline-name",
HelpText = "PipelineName is required to rule BA4001 and BA4002.")]
Copy link
Member

@michaelcfanning michaelcfanning Jul 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PipelineName

same note, what is this data? you don't describe it. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated the helptext, pls, review :)

public string PipelineName { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,7 @@ private void PrintCompilerData(BinaryAnalyzerContext context, string language, I

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();
context.CompilerDataLogger.Write(compiler.Compiler.ToString(), compiler.Version.ToString(), language, file);
Copy link
Member

@michaelcfanning michaelcfanning Jul 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CompilerDataLogger

why is it ok that some data has gotten dropped? such as the sha256 has? #ByDesign

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where I create the CompilerDataLogger, i pass the context. from it I retrieve information such as hash. With that, i can remove from here and just pass the data that i dont have during construction of compilerDataLogger

}
}
}
Expand Down
21 changes: 5 additions & 16 deletions src/BinSkim.Rules/PERules/BA4001.ReportPECompilerData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ 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);
Copy link
Member

@michaelcfanning michaelcfanning Jul 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CompilerDataLogger

what about the hash data? #ByDesign

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the hash is constructed with the CompilerDataLogger, so we don't need to worry here.

return;
}

Expand Down Expand Up @@ -83,7 +83,7 @@ public override void AnalyzePortableExecutableAndPdb(BinaryAnalyzerContext conte
omDetails.CompilerFrontEndVersion + "," +
omDetails.Language;

if (!records.TryGetValue(record, out ObjectModuleDetails value))
if (!records.ContainsKey(record))
{
records[record] = omDetails;
}
Expand All @@ -92,18 +92,7 @@ public override void AnalyzePortableExecutableAndPdb(BinaryAnalyzerContext conte

foreach (KeyValuePair<string, ObjectModuleDetails> 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);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/BinSkim.Sdk/BinSkim.Sdk.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.ApplicationInsights" Version="2.17.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="System.Collections.Immutable" Version="5.0.0" />
</ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions src/BinSkim.Sdk/BinaryAnalyzerContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public Uri TargetUri
this.uri = value;
}
}

public bool TracePdbLoads { get; set; }

public string SymbolPath { get; set; }
Expand All @@ -77,6 +78,8 @@ public string MimeType
public bool AnalysisComplete { get; set; }
public DefaultTraces Traces { get; set; }

public ICompilerDataLogger CompilerDataLogger { get; set; }

private bool disposed = false;

protected virtual void Dispose(bool disposing)
Expand Down
2 changes: 1 addition & 1 deletion src/BinSkim.Sdk/BinaryTargetManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
172 changes: 172 additions & 0 deletions src/BinSkim.Sdk/CompilerDataLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// 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.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 : ICompilerDataLogger
{
private const string CompilerEventName = "CompilerInformation";

private readonly bool appInsightsRegistered;

private readonly string sha256;
private readonly string localPath;
private readonly string repositoryUri;
private readonly string pipelineName;

private static TelemetryClient s_telemetryClient;
private static TelemetryConfiguration s_telemetryConfiguration;

public CompilerDataLogger(IAnalysisContext analysisContext, string repositoryUri, string pipelineName)
{
try
{
string appInsightsKey = Environment.GetEnvironmentVariable("BinskimAppInsightsKey");
if (!string.IsNullOrEmpty(appInsightsKey) && Guid.TryParse(appInsightsKey, out _))
{
Initialize(appInsightsKey);
appInsightsRegistered = true;
}
}
catch (SecurityException)
{
// User does not have access to retrieve information from environment variables.
}

this.pipelineName = pipelineName;
this.repositoryUri = repositoryUri;
this.sha256 = analysisContext?.Hashes?.Sha256;
this.localPath = analysisContext?.TargetUri?.LocalPath;
}

public static void Initialize(string instrumentationKey)
{
s_telemetryConfiguration = new TelemetryConfiguration(instrumentationKey);
s_telemetryClient = new TelemetryClient(s_telemetryConfiguration);
}

public static void Flush()
{
s_telemetryClient?.Flush();
}

public void PrintHeader()
{
if (!appInsightsRegistered)
{
Console.WriteLine("RepositoryUri,PipelineName,Target,Compiler Name,Compiler BackEnd Version,Compiler FrontEnd Version,Language,Dialect,Module Name,Module Library,Hash,Error");
}
}

public void Write(string compilerData, ObjectModuleDetails omDetails)
{
string name = omDetails?.Name?.Replace(",", "_");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name

are we really transforming data that previously existed in the PDB into some other normalized form?

this doesn't seem like a good idea, it seems better to preserve data as it actually exists in the binary.

can you explain the decision? certainly this code needs to be commented if we retain what you've done.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we were creating a csv and we were using ',' as separator.
with that, I saw cases where the name contained ',' breaking the csv.

this is why i changed from ',' to '_'

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typically, what you would do to workaround this would be to encapsulate the data in double quotes. is that an option here?

we really should not be modifying the compiler generated data here. how will you convert it back if you need to unify it again with actual PDB data?

string library = omDetails?.Library?.Replace(",", ";");
if (appInsightsRegistered)
{
string[] compilerDataParts = compilerData.Split(',');

s_telemetryClient.TrackEvent(CompilerEventName, properties: new Dictionary<string, string>
{
{ "repositoryUri", repositoryUri ?? string.Empty },
Copy link
Member

@michaelcfanning michaelcfanning Jul 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

repositoryUri

you say in your help comments that this data is required, but it doesn't look like it's required. it's optional.

i think you meant you say that this data is only consumed by the compiler reporting rules, and not relevant otherwise. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the relevant code to throw an argumentNullException in case repositoryUri or pipelineName is null/empty.

With that, i cleaned the code

{ "pipelineName", pipelineName ?? string.Empty },
{ "target", this.localPath },
Copy link
Member

@michaelcfanning michaelcfanning Jul 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

localPath

why are we sending absolute paths? is this right? might this data expose some unwanted information, like a user alias? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made a change to transform the localPath to a relativeFilePath base on the targetFileSpecifiers

{ "compilerName", compilerDataParts[0] },
{ "compilerBackEndVersion", compilerDataParts[1] },
Copy link
Member

@michaelcfanning michaelcfanning Jul 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compilerDataParts[1]

look at these 'magic constants' where we have special knowledge that backend version is in the first piece.

that tends to be pretty fragile, it might be better for this helper to break out all this data and require the caller to parse into the format.
#Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed!

{ "compilerFrontEndVersion", compilerDataParts[2] },
{ "language", compilerDataParts[3] },
{ "dialect", string.Empty },
{ "moduleName", name ?? string.Empty },
{ "moduleLibrary", (name == library ? string.Empty : library) },
{ "hash", this.sha256 ?? string.Empty },
{ "error", string.Empty }
});
}
else
{
Console.Write($"{repositoryUri},");
Copy link
Member

@michaelcfanning michaelcfanning Jul 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write

Note that it'd be more performant to concatenate all this information into a single string and then pass it to WriteLine.
#Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Console.Write($"{pipelineName},");
Console.Write($"{this.localPath},");
Console.Write($"{compilerData},");
Console.Write($",");
Console.Write($"{name},");
Console.Write($"{(name == library ? string.Empty : library)},");
Console.Write($"{this.sha256},");
Console.WriteLine();
}
}

public void Write(string compilerName, string version, string language, string file)
{
if (appInsightsRegistered)
{
s_telemetryClient.TrackEvent(CompilerEventName, properties: new Dictionary<string, string>
{
{ "repositoryUri", repositoryUri ?? string.Empty },
{ "pipelineName", pipelineName ?? string.Empty },
{ "target", this.localPath },
{ "compilerName", compilerName ?? string.Empty },
{ "compilerBackEndVersion", version ?? string.Empty },
{ "compilerFrontEndVersion", version ?? string.Empty },
{ "language", language ?? string.Empty },
{ "dialect", string.Empty },
{ "moduleName", file ?? string.Empty },
{ "moduleLibrary", string.Empty },
{ "hash", this.sha256 ?? string.Empty },
{ "error", string.Empty }
});
}
else
{
Console.Write($"{repositoryUri},");
Console.Write($"{pipelineName},");
Console.Write($"{this.localPath},");
Console.Write($"{compilerName},");
Console.Write($"{version},");
Console.Write($"{version},");
Console.Write($"{language},");
Console.Write($",");
Console.Write($"{file},");
Console.Write(",");
Console.Write($"{this.sha256},");
Console.WriteLine();
}
}

public void WriteException(string errorMessage)
{
if (appInsightsRegistered)
{
s_telemetryClient.TrackEvent(CompilerEventName, properties: new Dictionary<string, string>
{
{ "repositoryUri", repositoryUri ?? string.Empty },
{ "pipelineName", pipelineName ?? string.Empty },
{ "target", this.localPath },
{ "compilerName", string.Empty },
{ "compilerBackEndVersion", string.Empty },
{ "compilerFrontEndVersion", string.Empty },
{ "language", string.Empty },
{ "dialect", string.Empty },
{ "moduleName", string.Empty },
{ "moduleLibrary", string.Empty },
{ "hash", this.sha256 ?? string.Empty },
{ "error", errorMessage }
});
}
else
{
Console.Write(this.localPath + ",");
Console.WriteLine($",,,,,,,,,{this.sha256},{errorMessage}");
}
}
}
}
15 changes: 15 additions & 0 deletions src/BinSkim.Sdk/ICompilerDataLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.CodeAnalysis.BinaryParsers.ProgramDatabase;

namespace Microsoft.CodeAnalysis.IL.Sdk
{
public interface ICompilerDataLogger
Copy link
Member

@michaelcfanning michaelcfanning Jul 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ICompilerDataLogger

why did you create an interface? #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just deleted.
i thought it was going to be useful.

{
void Write(string compilerData, ObjectModuleDetails omDetails);
void Write(string compilerName, string version, string language, string file);
void WriteException(string errorMessage);
void PrintHeader();
}
}
6 changes: 3 additions & 3 deletions src/BinaryParsers/MachOBinary/MachOBinary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SingleMachOBinary> MachOs { get; }

Expand All @@ -68,7 +68,7 @@ public static bool CanLoadBinary(Uri uri)
/// <summary>
/// Unit type of Dwarf used..
eddynaka marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public DwarfUnitType DwarfUnitType { get; set; } = DwarfUnitType.Unknown;
public DwarfUnitType DwarfUnitType { get; set; }

/// <summary>
/// Gets address offset within module when it is loaded.
Expand Down Expand Up @@ -115,6 +115,6 @@ public DwarfLanguage GetLanguage()

public ICompiler[] Compilers => throw new NotImplementedException();

#endregion
#endregion IDwarfBinary interface
}
}
17 changes: 2 additions & 15 deletions src/BinaryParsers/MachOBinary/SingleMachOBinary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class SingleMachOBinary : BinaryBase, IDwarfBinary
public SingleMachOBinary(MachO singleMachO, Uri uri) : base(uri)
{
this.MachO = singleMachO;
LoadCompilationUnits();
CompilationUnits = LoadCompilationUnits();
}

public MachO MachO { get; }
Expand All @@ -44,20 +44,7 @@ public SingleMachOBinary(MachO singleMachO, Uri uri) : base(uri)

public IEnumerable<EntryPoint> EntryPoint => this.MachO.GetCommandsOfType<EntryPoint>();

private List<DwarfCompilationUnit> compilationUnits;

public List<DwarfCompilationUnit> CompilationUnits
{
get
{
if (compilationUnits == null)
{
compilationUnits = LoadCompilationUnits();
}

return compilationUnits;
}
}
public List<DwarfCompilationUnit> CompilationUnits { get; }

private List<DwarfLineNumberProgram> lineNumberPrograms;

Expand Down
Loading