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

Enable analysis for managed binary #335

Merged
merged 11 commits into from
Jan 25, 2021
65 changes: 49 additions & 16 deletions src/BinSkim.Rules/PERules/BA2004.EnableSecureSourceCodeHashing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,20 @@ public class EnableSecureSourceCodeHashing : WindowsBinaryAndPdbSkimmerBase, IOp
protected override IEnumerable<string> MessageResourceNames => new string[] {
nameof(RuleResources.BA2004_Pass),
nameof(RuleResources.BA2004_Warning_NativeWithInsecureStaticLibraryCompilands),
nameof(RuleResources.BA2004_Warning_Managed),
nameof(RuleResources.BA2004_Error_Managed),
nameof(RuleResources.BA2004_Error_NativeWithInsecureDirectCompilands),
nameof(RuleResources.NotApplicable_InvalidMetadata)
};

public override AnalysisApplicability CanAnalyzePE(PEBinary target, Sarif.PropertiesDictionary policy, out string reasonForNotAnalyzing)
{
PE portableExecutable = target.PE;
AnalysisApplicability result = AnalysisApplicability.NotApplicableToSpecifiedTarget;

reasonForNotAnalyzing = MetadataConditions.ImageIsILOnlyAssembly;
if (portableExecutable.IsILOnly) { return result; }

// TODO: currently our test binary for this check is a dll that does not
// compile against any external library. BinSkim regards this as a resource
// only binary and skips the test. We should improve the IsResourceOnly
// helper to properly identify this dependency-free test binary as code.
Pdb di = target.Pdb;

// reasonForNotAnalyzing = MetadataConditions.ImageIsResourceOnlyBinary;
// if (portableExecutable.IsResourceOnly) { return result; }
if (target.PE.IsManaged && di == null)
{
reasonForNotAnalyzing = MetadataConditions.CouldNotLoadPdb;
Copy link
Member

Choose a reason for hiding this comment

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

reasonForNotAnalyzing = MetadataConditions.CouldNotLoadPdb [](start = 16, length = 58)

in other checks, the absence of a PDB results in an error level notification, not a 'not applicable' message. 'Not applicable' is not the right return value here, as this return value means 'the binary isn't a valid scan target'. that's not true here, managed code is a valid scan target, the problem is that we can't analyze it due to a missing pdb. you should go look and see how the native pdb reading errors handle a missing pdb.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

can you validate again? pushed new changes. Thank you


In reply to: 563214549 [](ancestors = 563214549)

return AnalysisApplicability.NotApplicableToSpecifiedTarget;
}

reasonForNotAnalyzing = null;
return AnalysisApplicability.ApplicableToSpecifiedTarget;
Expand All @@ -64,11 +58,50 @@ public override void AnalyzePortableExecutableAndPdb(BinaryAnalyzerContext conte
AnalyzeNativeBinaryAndPdb(context);
}

#pragma warning disable IDE0060 // Remove unused parameter
private void AnalyzeManagedAssemblyAndPdb(BinaryAnalyzerContext context)
#pragma warning restore IDE0060 // Remove unused parameter
{
// TODO: use DiaSymReader?
PEBinary target = context.PEBinary();
Pdb di = target.Pdb;

// Checking if it is full pdb
if (di.FileType == PdbFileType.Windows)
{
if (!target.PE.IsChecksumAlgorithmSecureForFullPdb())
{
// '{0}' is a managed binary compiled with an insecure (SHA-1) source code hashing algorithm.
// SHA-1 is subject to collision attacks and its use can compromise supply chain integrity.
// Pass '-checksumalgorithm:SHA256' on the csc.exe command-line or populate the project
// <ChecksumAlgorithm> property with 'SHA256' to enable secure source code hashing.
context.Logger.Log(this,
RuleUtilities.BuildResult(ResultKind.Fail, context, null,
nameof(RuleResources.BA2004_Error_Managed),
context.TargetUri.GetFileName()));
return;
}
}
else
{
if (!target.PE.IsChecksumAlgorithmSecureForPortablePdb())
{
// '{0}' is a managed binary compiled with an insecure (SHA-1) source code hashing algorithm.
// SHA-1 is subject to collision attacks and its use can compromise supply chain integrity.
// Pass '-checksumalgorithm:SHA256' on the csc.exe command-line or populate the project
// <ChecksumAlgorithm> property with 'SHA256' to enable secure source code hashing.
context.Logger.Log(this,
RuleUtilities.BuildResult(ResultKind.Fail, context, null,
nameof(RuleResources.BA2004_Error_Managed),
context.TargetUri.GetFileName()));
return;
}
}

// '{0}' is a {1} binary which was compiled with a secure (SHA-256)
// source code hashing algorithm.
context.Logger.Log(this,
RuleUtilities.BuildResult(ResultKind.Pass, context, null,
nameof(RuleResources.BA2004_Pass),
context.TargetUri.GetFileName(),
"managed"));
}

public void AnalyzeNativeBinaryAndPdb(BinaryAnalyzerContext context)
Expand Down
18 changes: 9 additions & 9 deletions src/BinSkim.Rules/RuleResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/BinSkim.Rules/RuleResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ Modules triggering this check were:
<value>The PDB for '{0}' was found and loaded. Probing details:
{1}</value>
</data>
<data name="BA2004_Warning_Managed" xml:space="preserve">
<data name="BA2004_Error_Managed" xml:space="preserve">
<value>'{0}' is a managed binary compiled with an insecure (SHA-1) source code hashing algorithm. SHA-1 is subject to collision attacks and its use can compromise supply chain integrity. Pass '-checksumalgorithm:SHA256' on the csc.exe command-line or populate the project &lt;ChecksumAlgorithm&gt; property with 'SHA256' to enable secure source code hashing.</value>
</data>
<data name="BA2004_Pass" xml:space="preserve">
Expand Down
6 changes: 6 additions & 0 deletions src/BinaryParsers/BinaryParsers.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
<RootNamespace>Microsoft.CodeAnalysis.BinaryParsers</RootNamespace>
<TargetFramework>$(NetStandardVersion)</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Copy link
Member

@michaelcfanning michaelcfanning Jan 23, 2021

Choose a reason for hiding this comment

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

Can collapse this into a single property group if you remove the condition attribute #Closed

</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.DiaSymReader" Version="1.3.0" />
Expand Down
5 changes: 0 additions & 5 deletions src/BinaryParsers/PEBinary/PEBinary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,6 @@ public PEBinary(

private Pdb LoadPdb()
{
// We should never be required to load a PDB for a managed assembly that does
// not incorporate native code, as no managed-relevant rule currently crawls
// PDBs for its analysis.
Debug.Assert(!this.PE.IsManaged || this.PE.IsMixedMode);

Pdb pdb = null;
try
{
Expand Down
67 changes: 64 additions & 3 deletions src/BinaryParsers/PEBinary/PortableExecutable/PE.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
using System.Reflection.PortableExecutable;
using System.Security.Cryptography;

using Microsoft.DiaSymReader;
using Microsoft.DiaSymReader.Tools;

namespace Microsoft.CodeAnalysis.BinaryParsers.PortableExecutable
{
public class PE : IDisposable
Expand Down Expand Up @@ -262,7 +265,6 @@ public byte[] ImageBytes
}
}


/// <summary>
/// Calculate SHA1 over the file contents
/// </summary>
Expand Down Expand Up @@ -365,7 +367,6 @@ public long Length
}
}


/// <summary>
/// Windows OS file version information
/// </summary>
Expand All @@ -381,7 +382,6 @@ public FileVersionInfo FileVersion
}
}


public Packer Packer
{
get
Expand Down Expand Up @@ -789,5 +789,66 @@ public bool IsWixBinary
return this.isWixBinary.Value;
}
}

public bool IsChecksumAlgorithmSecureForPortablePdb()
Copy link
Member

Choose a reason for hiding this comment

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

IsChecksumAlgorithmSecureForPortablePdb [](start = 20, length = 39)

rather than creating these helpers, you should add a helper that simply returns the checksum algorithm for managed PDBs.

ManagedPdbSourceFileChecksumAlgorithm { get; }

Copy link
Member

Choose a reason for hiding this comment

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

This property s/be on the PDB class, not in the PE class.


In reply to: 563213952 [](ancestors = 563213952)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh...remembered why i didn't move the below logic to pdb.cs: to do th reading I need a few things: peReader and the metadatareader.


In reply to: 563214137 [](ancestors = 563214137,563213952)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed a little bit, let me know what do u think.


In reply to: 563297494 [](ancestors = 563297494,563214137,563213952)

{
const string sha256 = "8829d00f-11b8-4213-878b-770e8597ac16";
var sha256guid = new Guid(sha256);

if (this.peReader.TryOpenAssociatedPortablePdb(
Copy link
Member

@michaelcfanning michaelcfanning Jan 23, 2021

Choose a reason for hiding this comment

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

TryOpenAssociatedPortablePdb [](start = 30, length = 28)

eventually pdb location logic needs to understand binskim's '--local-symbol-directories' command-line arguments. let's open an issue and take this in a future change. #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.

issue opened: #338


In reply to: 563213769 [](ancestors = 563213769)

this.FileName,
filePath => File.Exists(filePath) ? File.OpenRead(filePath) : null,
out MetadataReaderProvider pdbProvider,
out _))
{
MetadataReader metadataReader = pdbProvider.GetMetadataReader();
foreach (DocumentHandle document in metadataReader.Documents)
{
Document doc = metadataReader.GetDocument(document);

Guid hashGuid = metadataReader.GetGuid(doc.HashAlgorithm);

return hashGuid == sha256guid;
}
}

return false;
Copy link
Member

@michaelcfanning michaelcfanning Jan 23, 2021

Choose a reason for hiding this comment

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

return false [](start = 12, length = 12)

Always prefer early exit if possible. So, change the code to say if (!TryOpen) then return false immediately. #Closed

}

public bool IsChecksumAlgorithmSecureForFullPdb()
{
const string sha256 = "8829d00f-11b8-4213-878b-770e8597ac16";
var sha256guid = new Guid(sha256);
Copy link
Member

@michaelcfanning michaelcfanning Jan 23, 2021

Choose a reason for hiding this comment

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

declare a private static readonly Guid that's initialized to this. #Closed

string fileName = Path.GetFileName(this.FileName);
string extension = Path.GetExtension(fileName);
string pdbPath = this.FileName.Replace(fileName, fileName.Replace(extension, ".pdb"));

if (!File.Exists(pdbPath))
{
return false;
}

using var pdbStream = new FileStream(pdbPath, FileMode.Open, FileAccess.Read);

var metadataProvider = new SymMetadataProvider(this.metadataReader);
object importer = SymUnmanagedReaderFactory.CreateSymReaderMetadataImport(metadataProvider);
ISymUnmanagedReader3 reader = SymUnmanagedReaderFactory.CreateReaderWithMetadataImport<ISymUnmanagedReader3>(pdbStream, importer, SymUnmanagedReaderCreationOptions.UseComRegistry);

try
{
Guid algorithm = Guid.Empty;
foreach (ISymUnmanagedDocument document in reader.GetDocuments())
{
document.GetChecksumAlgorithmId(ref algorithm);
return algorithm == sha256guid;
}
}
finally
{
_ = ((ISymUnmanagedDispose)reader).Destroy();
}

return false;
}
}
}
105 changes: 105 additions & 0 deletions src/BinaryParsers/PEBinary/PortableExecutable/SymMetadataProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;

namespace Microsoft.DiaSymReader.Tools
{
internal sealed class SymMetadataProvider : ISymWriterMetadataProvider, ISymReaderMetadataProvider
{
private readonly MetadataReader _reader;

internal SymMetadataProvider(MetadataReader reader)
{
_reader = reader;
}

public unsafe bool TryGetStandaloneSignature(int standaloneSignatureToken, out byte* signature, out int length)
{
var sigHandle = (StandaloneSignatureHandle)MetadataTokens.Handle(standaloneSignatureToken);
if (sigHandle.IsNil)
{
signature = null;
length = 0;
return false;
}

StandaloneSignature sig = _reader.GetStandaloneSignature(sigHandle);
BlobReader blobReader = _reader.GetBlobReader(sig.Signature);

signature = blobReader.StartPointer;
length = blobReader.Length;
return true;
}

public bool TryGetTypeDefinitionInfo(int typeDefinitionToken, [NotNullWhen(true)] out string namespaceName, [NotNullWhen(true)] out string typeName, out TypeAttributes attributes)
{
var handle = (TypeDefinitionHandle)MetadataTokens.Handle(typeDefinitionToken);
if (handle.IsNil)
{
namespaceName = null;
typeName = null;
attributes = 0;
return false;
}

TypeDefinition typeDefinition = _reader.GetTypeDefinition(handle);
namespaceName = _reader.GetString(typeDefinition.Namespace);
typeName = _reader.GetString(typeDefinition.Name);
attributes = typeDefinition.Attributes;
return true;
}

public bool TryGetTypeReferenceInfo(int typeReferenceToken, [NotNullWhen(true)] out string namespaceName, [NotNullWhen(true)] out string typeName)
{
var handle = (TypeReferenceHandle)MetadataTokens.Handle(typeReferenceToken);
if (handle.IsNil)
{
namespaceName = null;
typeName = null;
return false;
}

TypeReference typeReference = _reader.GetTypeReference(handle);
namespaceName = _reader.GetString(typeReference.Namespace);
typeName = _reader.GetString(typeReference.Name);
return true;
}

public bool TryGetEnclosingType(int nestedTypeToken, out int enclosingTypeToken)
{
TypeDefinition nestedTypeDef = _reader.GetTypeDefinition(MetadataTokens.TypeDefinitionHandle(nestedTypeToken));
TypeDefinitionHandle declaringTypeHandle = nestedTypeDef.GetDeclaringType();

if (declaringTypeHandle.IsNil)
{
enclosingTypeToken = 0;
return false;
}
else
{
enclosingTypeToken = MetadataTokens.GetToken(declaringTypeHandle);
return true;
}
}

public bool TryGetMethodInfo(int methodDefinitionToken, [NotNullWhen(true)] out string methodName, out int declaringTypeToken)
{
var handle = (MethodDefinitionHandle)MetadataTokens.Handle(methodDefinitionToken);
if (handle.IsNil)
{
methodName = null;
declaringTypeToken = 0;
return false;
}

MethodDefinition methodDefinition = _reader.GetMethodDefinition(handle);
methodName = _reader.GetString(methodDefinition.Name);
declaringTypeToken = MetadataTokens.GetToken(methodDefinition.GetDeclaringType());
return true;
}
}
}
Loading