Add comprehensive tests for Go and Python version conflict detection and licensing normalization
- Implemented GoVersionConflictDetectorTests to validate pseudo-version detection, conflict analysis, and conflict retrieval for Go modules. - Created VersionConflictDetectorTests for Python to assess conflict detection across various version scenarios, including major, minor, and patch differences. - Added SpdxLicenseNormalizerTests to ensure accurate normalization of SPDX license strings and classifiers. - Developed VendoredPackageDetectorTests to identify vendored packages and extract embedded packages from Python packages, including handling of vendor directories and known vendored packages.
This commit is contained in:
@@ -321,4 +321,106 @@ public class ElfDynamicSectionParserTests
|
||||
buffer[offset + bytes.Length] = 0; // null terminator
|
||||
return bytes.Length;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParsesElfWithVersionNeeds()
|
||||
{
|
||||
// Test that version needs (GLIBC_2.17, etc.) are properly extracted
|
||||
var buffer = new byte[4096];
|
||||
SetupElf64Header(buffer, littleEndian: true);
|
||||
|
||||
// String table at offset 0x400
|
||||
var strtab = 0x400;
|
||||
var libcOffset = 1; // "libc.so.6"
|
||||
var glibc217Offset = libcOffset + WriteString(buffer, strtab + libcOffset, "libc.so.6") + 1;
|
||||
var glibc228Offset = glibc217Offset + WriteString(buffer, strtab + glibc217Offset, "GLIBC_2.17") + 1;
|
||||
var strtabSize = glibc228Offset + WriteString(buffer, strtab + glibc228Offset, "GLIBC_2.28") + 1;
|
||||
|
||||
// Section headers at offset 0x800
|
||||
var shoff = 0x800;
|
||||
var shentsize = 64;
|
||||
var shnum = 3; // null + .dynstr + .gnu.version_r
|
||||
|
||||
BitConverter.GetBytes((ulong)shoff).CopyTo(buffer, 40);
|
||||
BitConverter.GetBytes((ushort)shentsize).CopyTo(buffer, 58);
|
||||
BitConverter.GetBytes((ushort)shnum).CopyTo(buffer, 60);
|
||||
|
||||
// Section header 0: null
|
||||
// Section header 1: .dynstr
|
||||
var sh1 = shoff + shentsize;
|
||||
BitConverter.GetBytes((uint)3).CopyTo(buffer, sh1 + 4); // sh_type = SHT_STRTAB
|
||||
BitConverter.GetBytes((ulong)0x400).CopyTo(buffer, sh1 + 16); // sh_addr
|
||||
BitConverter.GetBytes((ulong)strtab).CopyTo(buffer, sh1 + 24); // sh_offset
|
||||
BitConverter.GetBytes((ulong)strtabSize).CopyTo(buffer, sh1 + 32); // sh_size
|
||||
|
||||
// Section header 2: .gnu.version_r (SHT_GNU_verneed = 0x6ffffffe)
|
||||
var verneedFileOffset = 0x600;
|
||||
var sh2 = shoff + shentsize * 2;
|
||||
BitConverter.GetBytes((uint)0x6ffffffe).CopyTo(buffer, sh2 + 4); // sh_type = SHT_GNU_verneed
|
||||
BitConverter.GetBytes((ulong)0x600).CopyTo(buffer, sh2 + 16); // sh_addr (vaddr)
|
||||
BitConverter.GetBytes((ulong)verneedFileOffset).CopyTo(buffer, sh2 + 24); // sh_offset
|
||||
|
||||
// Version needs section at offset 0x600
|
||||
// Verneed entry for libc.so.6 with two version requirements
|
||||
// Elf64_Verneed: vn_version(2), vn_cnt(2), vn_file(4), vn_aux(4), vn_next(4)
|
||||
var verneedOffset = verneedFileOffset;
|
||||
BitConverter.GetBytes((ushort)1).CopyTo(buffer, verneedOffset); // vn_version = 1
|
||||
BitConverter.GetBytes((ushort)2).CopyTo(buffer, verneedOffset + 2); // vn_cnt = 2 aux entries
|
||||
BitConverter.GetBytes((uint)libcOffset).CopyTo(buffer, verneedOffset + 4); // vn_file -> "libc.so.6"
|
||||
BitConverter.GetBytes((uint)16).CopyTo(buffer, verneedOffset + 8); // vn_aux = 16 (offset to first aux)
|
||||
BitConverter.GetBytes((uint)0).CopyTo(buffer, verneedOffset + 12); // vn_next = 0 (last entry)
|
||||
|
||||
// Vernaux entries
|
||||
// Elf64_Vernaux: vna_hash(4), vna_flags(2), vna_other(2), vna_name(4), vna_next(4)
|
||||
var aux1Offset = verneedOffset + 16;
|
||||
BitConverter.GetBytes((uint)0x0d696910).CopyTo(buffer, aux1Offset); // vna_hash for GLIBC_2.17
|
||||
BitConverter.GetBytes((ushort)0).CopyTo(buffer, aux1Offset + 4); // vna_flags
|
||||
BitConverter.GetBytes((ushort)2).CopyTo(buffer, aux1Offset + 6); // vna_other
|
||||
BitConverter.GetBytes((uint)glibc217Offset).CopyTo(buffer, aux1Offset + 8); // vna_name -> "GLIBC_2.17"
|
||||
BitConverter.GetBytes((uint)16).CopyTo(buffer, aux1Offset + 12); // vna_next = 16 (offset to next aux)
|
||||
|
||||
var aux2Offset = aux1Offset + 16;
|
||||
BitConverter.GetBytes((uint)0x09691974).CopyTo(buffer, aux2Offset); // vna_hash for GLIBC_2.28
|
||||
BitConverter.GetBytes((ushort)0).CopyTo(buffer, aux2Offset + 4);
|
||||
BitConverter.GetBytes((ushort)3).CopyTo(buffer, aux2Offset + 6);
|
||||
BitConverter.GetBytes((uint)glibc228Offset).CopyTo(buffer, aux2Offset + 8); // vna_name -> "GLIBC_2.28"
|
||||
BitConverter.GetBytes((uint)0).CopyTo(buffer, aux2Offset + 12); // vna_next = 0 (last aux)
|
||||
|
||||
// Dynamic section at offset 0x200
|
||||
var dynOffset = 0x200;
|
||||
var dynEntrySize = 16;
|
||||
var dynIndex = 0;
|
||||
|
||||
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex++, 5, 0x400); // DT_STRTAB
|
||||
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex++, 10, (ulong)strtabSize); // DT_STRSZ
|
||||
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex++, 1, (ulong)libcOffset); // DT_NEEDED -> libc.so.6
|
||||
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex++, 0x6ffffffe, 0x600); // DT_VERNEED (vaddr)
|
||||
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex++, 0x6fffffff, 1); // DT_VERNEEDNUM = 1
|
||||
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex, 0, 0); // DT_NULL
|
||||
|
||||
var dynSize = dynEntrySize * (dynIndex + 1);
|
||||
|
||||
// Program header
|
||||
var phoff = 0x40;
|
||||
var phentsize = 56;
|
||||
var phnum = 1;
|
||||
|
||||
BitConverter.GetBytes((ulong)phoff).CopyTo(buffer, 32);
|
||||
BitConverter.GetBytes((ushort)phentsize).CopyTo(buffer, 54);
|
||||
BitConverter.GetBytes((ushort)phnum).CopyTo(buffer, 56);
|
||||
|
||||
BitConverter.GetBytes((uint)2).CopyTo(buffer, phoff); // PT_DYNAMIC
|
||||
BitConverter.GetBytes((ulong)dynOffset).CopyTo(buffer, phoff + 8);
|
||||
BitConverter.GetBytes((ulong)dynSize).CopyTo(buffer, phoff + 32);
|
||||
|
||||
using var stream = new MemoryStream(buffer);
|
||||
var result = ElfDynamicSectionParser.TryParse(stream, out var info);
|
||||
|
||||
result.Should().BeTrue();
|
||||
info.Dependencies.Should().HaveCount(1);
|
||||
info.Dependencies[0].Soname.Should().Be("libc.so.6");
|
||||
info.Dependencies[0].VersionNeeds.Should().HaveCount(2);
|
||||
info.Dependencies[0].VersionNeeds.Should().Contain(v => v.Version == "GLIBC_2.17");
|
||||
info.Dependencies[0].VersionNeeds.Should().Contain(v => v.Version == "GLIBC_2.28");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,4 +275,226 @@ public class PeImportParserTests
|
||||
""";
|
||||
Encoding.UTF8.GetBytes(manifestXml).CopyTo(buffer, 0x1000);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParsesPe32PlusWithImportThunks()
|
||||
{
|
||||
// Test that 64-bit PE files correctly parse 8-byte import thunks
|
||||
var buffer = new byte[8192];
|
||||
SetupPe32PlusHeaderWithImports(buffer);
|
||||
|
||||
using var stream = new MemoryStream(buffer);
|
||||
var result = PeImportParser.TryParse(stream, out var info);
|
||||
|
||||
result.Should().BeTrue();
|
||||
info.Is64Bit.Should().BeTrue();
|
||||
info.Dependencies.Should().HaveCount(1);
|
||||
info.Dependencies[0].DllName.Should().Be("kernel32.dll");
|
||||
// Verify function names are parsed correctly with 8-byte thunks
|
||||
info.Dependencies[0].ImportedFunctions.Should().Contain("GetProcAddress");
|
||||
info.Dependencies[0].ImportedFunctions.Should().Contain("LoadLibraryA");
|
||||
}
|
||||
|
||||
private static void SetupPe32PlusHeaderWithImports(byte[] buffer)
|
||||
{
|
||||
// DOS header
|
||||
buffer[0] = (byte)'M';
|
||||
buffer[1] = (byte)'Z';
|
||||
BitConverter.GetBytes(0x80).CopyTo(buffer, 0x3C); // e_lfanew
|
||||
|
||||
// PE signature
|
||||
var peOffset = 0x80;
|
||||
buffer[peOffset] = (byte)'P';
|
||||
buffer[peOffset + 1] = (byte)'E';
|
||||
|
||||
// COFF header
|
||||
BitConverter.GetBytes((ushort)0x8664).CopyTo(buffer, peOffset + 4); // Machine = x86_64
|
||||
BitConverter.GetBytes((ushort)2).CopyTo(buffer, peOffset + 6); // NumberOfSections
|
||||
BitConverter.GetBytes((ushort)0xF0).CopyTo(buffer, peOffset + 20); // SizeOfOptionalHeader (PE32+)
|
||||
|
||||
// Optional header (PE32+)
|
||||
var optHeaderOffset = peOffset + 24;
|
||||
BitConverter.GetBytes((ushort)0x20b).CopyTo(buffer, optHeaderOffset); // Magic = PE32+
|
||||
BitConverter.GetBytes((ushort)PeSubsystem.WindowsConsole).CopyTo(buffer, optHeaderOffset + 68); // Subsystem
|
||||
BitConverter.GetBytes((uint)16).CopyTo(buffer, optHeaderOffset + 108); // NumberOfRvaAndSizes
|
||||
|
||||
// Data directory - Import Directory (entry 1)
|
||||
var dataDirOffset = optHeaderOffset + 112;
|
||||
BitConverter.GetBytes((uint)0x2000).CopyTo(buffer, dataDirOffset + 8); // Import Directory RVA
|
||||
BitConverter.GetBytes((uint)40).CopyTo(buffer, dataDirOffset + 12); // Import Directory Size
|
||||
|
||||
// Section headers
|
||||
var sectionOffset = optHeaderOffset + 0xF0;
|
||||
|
||||
// .text section
|
||||
".text\0\0\0"u8.CopyTo(buffer.AsSpan(sectionOffset));
|
||||
BitConverter.GetBytes((uint)0x1000).CopyTo(buffer, sectionOffset + 8); // VirtualSize
|
||||
BitConverter.GetBytes((uint)0x1000).CopyTo(buffer, sectionOffset + 12); // VirtualAddress
|
||||
BitConverter.GetBytes((uint)0x200).CopyTo(buffer, sectionOffset + 16); // SizeOfRawData
|
||||
BitConverter.GetBytes((uint)0x200).CopyTo(buffer, sectionOffset + 20); // PointerToRawData
|
||||
|
||||
// .idata section
|
||||
sectionOffset += 40;
|
||||
".idata\0\0"u8.CopyTo(buffer.AsSpan(sectionOffset));
|
||||
BitConverter.GetBytes((uint)0x1000).CopyTo(buffer, sectionOffset + 8); // VirtualSize
|
||||
BitConverter.GetBytes((uint)0x2000).CopyTo(buffer, sectionOffset + 12); // VirtualAddress
|
||||
BitConverter.GetBytes((uint)0x1000).CopyTo(buffer, sectionOffset + 16); // SizeOfRawData
|
||||
BitConverter.GetBytes((uint)0x400).CopyTo(buffer, sectionOffset + 20); // PointerToRawData
|
||||
|
||||
// Import descriptor at file offset 0x400 (RVA 0x2000)
|
||||
var importOffset = 0x400;
|
||||
BitConverter.GetBytes((uint)0x2080).CopyTo(buffer, importOffset); // OriginalFirstThunk RVA
|
||||
BitConverter.GetBytes((uint)0).CopyTo(buffer, importOffset + 4); // TimeDateStamp
|
||||
BitConverter.GetBytes((uint)0).CopyTo(buffer, importOffset + 8); // ForwarderChain
|
||||
BitConverter.GetBytes((uint)0x2100).CopyTo(buffer, importOffset + 12); // Name RVA
|
||||
BitConverter.GetBytes((uint)0x2080).CopyTo(buffer, importOffset + 16); // FirstThunk
|
||||
|
||||
// Null terminator for import directory
|
||||
// (already zero at importOffset + 20)
|
||||
|
||||
// Import Lookup Table (ILT) / Import Name Table at RVA 0x2080 -> file offset 0x480
|
||||
// PE32+ uses 8-byte entries!
|
||||
var iltOffset = 0x480;
|
||||
// Entry 1: Import by name, hint-name RVA = 0x2120
|
||||
BitConverter.GetBytes((ulong)0x2120).CopyTo(buffer, iltOffset);
|
||||
// Entry 2: Import by name, hint-name RVA = 0x2140
|
||||
BitConverter.GetBytes((ulong)0x2140).CopyTo(buffer, iltOffset + 8);
|
||||
// Null terminator (8 bytes of zero)
|
||||
// (already zero)
|
||||
|
||||
// DLL name at RVA 0x2100 -> file offset 0x500
|
||||
"kernel32.dll\0"u8.CopyTo(buffer.AsSpan(0x500));
|
||||
|
||||
// Hint-Name table entries
|
||||
// Entry 1 at RVA 0x2120 -> file offset 0x520
|
||||
BitConverter.GetBytes((ushort)0).CopyTo(buffer, 0x520); // Hint
|
||||
"GetProcAddress\0"u8.CopyTo(buffer.AsSpan(0x522));
|
||||
|
||||
// Entry 2 at RVA 0x2140 -> file offset 0x540
|
||||
BitConverter.GetBytes((ushort)0).CopyTo(buffer, 0x540); // Hint
|
||||
"LoadLibraryA\0"u8.CopyTo(buffer.AsSpan(0x542));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParsesPeWithEmbeddedResourceManifest()
|
||||
{
|
||||
// Test that manifest is properly extracted from PE resources
|
||||
var buffer = new byte[16384];
|
||||
SetupPe32HeaderWithResourceManifest(buffer);
|
||||
|
||||
using var stream = new MemoryStream(buffer);
|
||||
var result = PeImportParser.TryParse(stream, out var info);
|
||||
|
||||
result.Should().BeTrue();
|
||||
info.SxsDependencies.Should().HaveCountGreaterOrEqualTo(1);
|
||||
info.SxsDependencies.Should().Contain(d => d.Name == "Microsoft.VC90.CRT");
|
||||
}
|
||||
|
||||
private static void SetupPe32HeaderWithResourceManifest(byte[] buffer)
|
||||
{
|
||||
// DOS header
|
||||
buffer[0] = (byte)'M';
|
||||
buffer[1] = (byte)'Z';
|
||||
BitConverter.GetBytes(0x80).CopyTo(buffer, 0x3C);
|
||||
|
||||
// PE signature
|
||||
var peOffset = 0x80;
|
||||
buffer[peOffset] = (byte)'P';
|
||||
buffer[peOffset + 1] = (byte)'E';
|
||||
|
||||
// COFF header
|
||||
BitConverter.GetBytes((ushort)0x8664).CopyTo(buffer, peOffset + 4);
|
||||
BitConverter.GetBytes((ushort)2).CopyTo(buffer, peOffset + 6); // 2 sections
|
||||
BitConverter.GetBytes((ushort)0xE0).CopyTo(buffer, peOffset + 20);
|
||||
|
||||
// Optional header (PE32)
|
||||
var optHeaderOffset = peOffset + 24;
|
||||
BitConverter.GetBytes((ushort)0x10b).CopyTo(buffer, optHeaderOffset);
|
||||
BitConverter.GetBytes((ushort)PeSubsystem.WindowsConsole).CopyTo(buffer, optHeaderOffset + 68);
|
||||
BitConverter.GetBytes((uint)16).CopyTo(buffer, optHeaderOffset + 92);
|
||||
|
||||
// Data directory - Resource Directory (entry 2)
|
||||
var dataDirOffset = optHeaderOffset + 96;
|
||||
BitConverter.GetBytes((uint)0x3000).CopyTo(buffer, dataDirOffset + 16); // Resource Directory RVA
|
||||
BitConverter.GetBytes((uint)0x1000).CopyTo(buffer, dataDirOffset + 20); // Resource Directory Size
|
||||
|
||||
// Section headers
|
||||
var sectionOffset = optHeaderOffset + 0xE0;
|
||||
|
||||
// .text section
|
||||
".text\0\0\0"u8.CopyTo(buffer.AsSpan(sectionOffset));
|
||||
BitConverter.GetBytes((uint)0x1000).CopyTo(buffer, sectionOffset + 8);
|
||||
BitConverter.GetBytes((uint)0x1000).CopyTo(buffer, sectionOffset + 12);
|
||||
BitConverter.GetBytes((uint)0x200).CopyTo(buffer, sectionOffset + 16);
|
||||
BitConverter.GetBytes((uint)0x200).CopyTo(buffer, sectionOffset + 20);
|
||||
|
||||
// .rsrc section
|
||||
sectionOffset += 40;
|
||||
".rsrc\0\0\0"u8.CopyTo(buffer.AsSpan(sectionOffset));
|
||||
BitConverter.GetBytes((uint)0x1000).CopyTo(buffer, sectionOffset + 8);
|
||||
BitConverter.GetBytes((uint)0x3000).CopyTo(buffer, sectionOffset + 12); // VirtualAddress
|
||||
BitConverter.GetBytes((uint)0x1000).CopyTo(buffer, sectionOffset + 16);
|
||||
BitConverter.GetBytes((uint)0x1000).CopyTo(buffer, sectionOffset + 20); // PointerToRawData
|
||||
|
||||
// Resource directory at file offset 0x1000 (RVA 0x3000)
|
||||
var rsrcBase = 0x1000;
|
||||
|
||||
// Root directory (Type level)
|
||||
BitConverter.GetBytes((uint)0).CopyTo(buffer, rsrcBase); // Characteristics
|
||||
BitConverter.GetBytes((uint)0).CopyTo(buffer, rsrcBase + 4); // TimeDateStamp
|
||||
BitConverter.GetBytes((ushort)0).CopyTo(buffer, rsrcBase + 8); // MajorVersion
|
||||
BitConverter.GetBytes((ushort)0).CopyTo(buffer, rsrcBase + 10); // MinorVersion
|
||||
BitConverter.GetBytes((ushort)0).CopyTo(buffer, rsrcBase + 12); // NumberOfNamedEntries
|
||||
BitConverter.GetBytes((ushort)1).CopyTo(buffer, rsrcBase + 14); // NumberOfIdEntries
|
||||
|
||||
// Entry for RT_MANIFEST (ID=24) at offset 16
|
||||
BitConverter.GetBytes((uint)24).CopyTo(buffer, rsrcBase + 16); // ID = RT_MANIFEST
|
||||
BitConverter.GetBytes((uint)(0x80000000 | 0x30)).CopyTo(buffer, rsrcBase + 20); // Offset to subdirectory (high bit set)
|
||||
|
||||
// Name/ID subdirectory at offset 0x30
|
||||
var nameDir = rsrcBase + 0x30;
|
||||
BitConverter.GetBytes((uint)0).CopyTo(buffer, nameDir);
|
||||
BitConverter.GetBytes((uint)0).CopyTo(buffer, nameDir + 4);
|
||||
BitConverter.GetBytes((ushort)0).CopyTo(buffer, nameDir + 8);
|
||||
BitConverter.GetBytes((ushort)0).CopyTo(buffer, nameDir + 10);
|
||||
BitConverter.GetBytes((ushort)0).CopyTo(buffer, nameDir + 12);
|
||||
BitConverter.GetBytes((ushort)1).CopyTo(buffer, nameDir + 14);
|
||||
|
||||
// Entry for ID=1 (application manifest)
|
||||
BitConverter.GetBytes((uint)1).CopyTo(buffer, nameDir + 16);
|
||||
BitConverter.GetBytes((uint)(0x80000000 | 0x50)).CopyTo(buffer, nameDir + 20); // Offset to language subdirectory
|
||||
|
||||
// Language subdirectory at offset 0x50
|
||||
var langDir = rsrcBase + 0x50;
|
||||
BitConverter.GetBytes((uint)0).CopyTo(buffer, langDir);
|
||||
BitConverter.GetBytes((uint)0).CopyTo(buffer, langDir + 4);
|
||||
BitConverter.GetBytes((ushort)0).CopyTo(buffer, langDir + 8);
|
||||
BitConverter.GetBytes((ushort)0).CopyTo(buffer, langDir + 10);
|
||||
BitConverter.GetBytes((ushort)0).CopyTo(buffer, langDir + 12);
|
||||
BitConverter.GetBytes((ushort)1).CopyTo(buffer, langDir + 14);
|
||||
|
||||
// Entry for language (e.g., 0x409 = English US)
|
||||
BitConverter.GetBytes((uint)0x409).CopyTo(buffer, langDir + 16);
|
||||
BitConverter.GetBytes((uint)0x70).CopyTo(buffer, langDir + 20); // Offset to data entry (no high bit = data entry)
|
||||
|
||||
// Data entry at offset 0x70
|
||||
var dataEntry = rsrcBase + 0x70;
|
||||
BitConverter.GetBytes((uint)0x3100).CopyTo(buffer, dataEntry); // Data RVA
|
||||
BitConverter.GetBytes((uint)0x200).CopyTo(buffer, dataEntry + 4); // Data Size
|
||||
BitConverter.GetBytes((uint)0).CopyTo(buffer, dataEntry + 8); // CodePage
|
||||
BitConverter.GetBytes((uint)0).CopyTo(buffer, dataEntry + 12); // Reserved
|
||||
|
||||
// Manifest data at RVA 0x3100 -> file offset 0x1100
|
||||
var manifestXml = """
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.VC90.CRT" version="9.0.21022.8" processorArchitecture="amd64" publicKeyToken="1fc8b3b9a1e18e3b"/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
""";
|
||||
Encoding.UTF8.GetBytes(manifestXml).CopyTo(buffer, 0x1100);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user