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:
StellaOps Bot
2025-12-07 01:51:37 +02:00
parent 98934170ca
commit e0f6efecce
66 changed files with 7591 additions and 451 deletions

View File

@@ -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");
}
}

View File

@@ -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);
}
}