Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
185 changes: 183 additions & 2 deletions tracer/src/Datadog.Trace.Coverage.collector/AssemblyProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ internal class AssemblyProcessor
"Xunit.SkippableFact.dll",
};

private static readonly string[] IgnoredAssemblyPrefixes =
{
"coverlet.",
};

private readonly CoverageSettings _settings;
private readonly ICollectorLogger _logger;
private readonly string _assemblyFilePath;
Expand Down Expand Up @@ -97,9 +102,10 @@ public unsafe void Process()
_logger.Debug($"Processing: {_assemblyFilePath}");

// Check if the assembly is in the ignored assemblies list.
var assemblyFullName = Path.GetFileName(_assemblyFilePath);
if (Array.Exists(IgnoredAssemblies, i => assemblyFullName == i))
var assemblyFileName = Path.GetFileName(_assemblyFilePath);
if (IsIgnoredAssembly(assemblyFileName))
{
_logger.Debug($"Assembly: {FilePath}, ignored by assembly name.");
return;
}

Expand Down Expand Up @@ -170,6 +176,18 @@ public unsafe void Process()
_logger.Debug($"{snkFilePath} exists.");
_strongNameKeyBlob = File.ReadAllBytes(snkFilePath);
_logger.Debug($"{snkFilePath} loaded.");

if (!TryGetStrongNamePublicKey(_strongNameKeyBlob, out _))
{
_logger.Warning($"Assembly: {FilePath}, the .snk file configured in {Configuration.ConfigurationKeys.CIVisibility.CodeCoverageSnkFile} could not be read. Skipping coverage instrumentation to avoid changing the assembly identity.");
return;
}

if (!StrongNameKeyMatchesAssemblyPublicKey(assemblyDefinition.Name, _strongNameKeyBlob))
{
_logger.Warning($"Assembly: {FilePath}, is signed with a different strong-name key than the .snk file configured in {Configuration.ConfigurationKeys.CIVisibility.CodeCoverageSnkFile}. Skipping coverage instrumentation to avoid changing the assembly identity.");
return;
}
}
else if (tracerTarget == TracerTarget.Net461)
{
Expand Down Expand Up @@ -774,6 +792,169 @@ internal static void WriteTargetAssembly(AssemblyDefinition assemblyDefinition,
});
}

internal static bool IsIgnoredAssembly(string? assemblyFileName)
{
if (assemblyFileName is not { Length: > 0 } fileName)
{
return false;
}

return Array.Exists(IgnoredAssemblies, ignoredAssembly => string.Equals(fileName, ignoredAssembly, StringComparison.OrdinalIgnoreCase)) ||
Array.Exists(IgnoredAssemblyPrefixes, ignoredPrefix => fileName.StartsWith(ignoredPrefix, StringComparison.OrdinalIgnoreCase));
}

internal static bool IsIgnoredAssemblyDependencyManifest(string? depsJsonFileName)
{
const string depsJsonSuffix = ".deps.json";
if (depsJsonFileName is not { Length: > 0 } fileName ||
!fileName.EndsWith(depsJsonSuffix, StringComparison.OrdinalIgnoreCase))
{
return false;
}

var assemblyFileName = fileName.Substring(0, fileName.Length - depsJsonSuffix.Length) + ".dll";
return IsIgnoredAssembly(assemblyFileName);
}

// Cecil derives the assembly identity from the strong-name public key blob before signing.
// Mirror that conversion so mismatched configured SNK files are detected before rewriting.
internal static bool StrongNameKeyMatchesAssemblyPublicKey(AssemblyNameDefinition assemblyName, byte[] strongNameKeyBlob)
{
return TryGetStrongNamePublicKey(strongNameKeyBlob, out var publicKey) &&
assemblyName.PublicKey is { Length: > 0 } assemblyPublicKey &&
assemblyPublicKey.SequenceEqual(publicKey);
}

internal static bool TryGetStrongNamePublicKey(byte[]? strongNameKeyBlob, out byte[] publicKey)
{
publicKey = Array.Empty<byte>();

if (strongNameKeyBlob is not { Length: > 20 })
{
return false;
}

if (IsStrongNamePublicKeyBlob(strongNameKeyBlob))
{
publicKey = strongNameKeyBlob;
return true;
}

byte[] capiPublicKeyBlob;
if (IsCapiPublicKeyBlob(strongNameKeyBlob))
{
capiPublicKeyBlob = strongNameKeyBlob;
}
else if (!TryGetCapiPublicKeyBlobFromPrivateKey(strongNameKeyBlob, out capiPublicKeyBlob))
{
return false;
}

publicKey = CreateStrongNamePublicKey(capiPublicKeyBlob);
return true;
}

private static bool IsStrongNamePublicKeyBlob(byte[] blob)
{
return blob.Length > 12 &&
blob[0] == 0x00 &&
blob[12] == 0x06 &&
IsCapiPublicKeyBlob(blob, 12);
}

private static bool IsCapiPublicKeyBlob(byte[] blob, int offset = 0)
{
if (blob.Length < offset + 20 ||
blob[offset] != 0x06 ||
blob[offset + 1] != 0x02 ||
blob[offset + 2] != 0x00 ||
blob[offset + 3] != 0x00 ||
ReadUInt32LittleEndian(blob, offset + 8) != 0x31415352)
{
return false;
}

var bitLength = ReadInt32LittleEndian(blob, offset + 12);
if (bitLength <= 0 || bitLength % 8 != 0)
{
return false;
}

var keyLength = bitLength / 8;
return blob.Length >= offset + 20 + keyLength;
}

private static bool TryGetCapiPublicKeyBlobFromPrivateKey(byte[] privateKeyBlob, out byte[] publicKeyBlob)
{
publicKeyBlob = Array.Empty<byte>();

if (privateKeyBlob.Length < 20 ||
privateKeyBlob[0] != 0x07 ||
privateKeyBlob[1] != 0x02 ||
privateKeyBlob[2] != 0x00 ||
privateKeyBlob[3] != 0x00 ||
ReadUInt32LittleEndian(privateKeyBlob, 8) != 0x32415352)
{
return false;
}

var bitLength = ReadInt32LittleEndian(privateKeyBlob, 12);
if (bitLength <= 0 || bitLength % 8 != 0)
{
return false;
}

var keyLength = bitLength / 8;
if (privateKeyBlob.Length < 20 + keyLength)
{
return false;
}

publicKeyBlob = new byte[20 + keyLength];
publicKeyBlob[0] = 0x06;
publicKeyBlob[1] = 0x02;
publicKeyBlob[5] = 0x24;
publicKeyBlob[8] = 0x52;
publicKeyBlob[9] = 0x53;
publicKeyBlob[10] = 0x41;
publicKeyBlob[11] = 0x31;
Buffer.BlockCopy(privateKeyBlob, 12, publicKeyBlob, 12, 8);
Buffer.BlockCopy(privateKeyBlob, 20, publicKeyBlob, 20, keyLength);
return true;
}

private static byte[] CreateStrongNamePublicKey(byte[] capiPublicKeyBlob)
{
var publicKey = new byte[12 + capiPublicKeyBlob.Length];
Buffer.BlockCopy(capiPublicKeyBlob, 0, publicKey, 12, capiPublicKeyBlob.Length);
publicKey[1] = 0x24;
publicKey[4] = 0x04;
publicKey[5] = 0x80;
WriteInt32LittleEndian(publicKey, 8, capiPublicKeyBlob.Length);
return publicKey;
}

private static int ReadInt32LittleEndian(byte[] buffer, int offset)
{
return (int)ReadUInt32LittleEndian(buffer, offset);
}

private static uint ReadUInt32LittleEndian(byte[] buffer, int offset)
{
return buffer[offset] |
((uint)buffer[offset + 1] << 8) |
((uint)buffer[offset + 2] << 16) |
((uint)buffer[offset + 3] << 24);
}

private static void WriteInt32LittleEndian(byte[] buffer, int offset, int value)
{
buffer[offset] = (byte)value;
buffer[offset + 1] = (byte)(value >> 8);
buffer[offset + 2] = (byte)(value >> 16);
buffer[offset + 3] = (byte)(value >> 24);
}

private static void RemoveShortOpCodes(Instruction instruction)
{
if (instruction.OpCode == OpCodes.Br_S) { instruction.OpCode = OpCodes.Br; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,12 @@ private void ProcessFolder(string folder, SearchOption searchOption)
var version = typeof(Instrumentation).Assembly.GetName().Version?.ToString();
foreach (var depsJsonPath in Directory.EnumerateFiles(directory, "*.deps.json", SearchOption.TopDirectoryOnly))
{
if (AssemblyProcessor.IsIgnoredAssemblyDependencyManifest(Path.GetFileName(depsJsonPath)))
{
_logger?.Debug($"Skipping dependency manifest for ignored assembly: {depsJsonPath}");
continue;
}

try
{
var json = JObject.Parse(File.ReadAllText(depsJsonPath));
Expand Down
Loading
Loading