feat: Add native binary analyzer test utilities and implement SM2 signing tests
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled

- Introduced `NativeTestBase` class for ELF, PE, and Mach-O binary parsing helpers and assertions.
- Created `TestCryptoFactory` for SM2 cryptographic provider setup and key generation.
- Implemented `Sm2SigningTests` to validate signing functionality with environment gate checks.
- Developed console export service and store with comprehensive unit tests for export status management.
This commit is contained in:
StellaOps Bot
2025-12-07 13:12:41 +02:00
parent d907729778
commit e53a282fbe
387 changed files with 21941 additions and 1518 deletions

View File

@@ -1,20 +1,19 @@
using System.Text;
using FluentAssertions;
using StellaOps.Scanner.Analyzers.Native;
using StellaOps.Scanner.Analyzers.Native.Tests.Fixtures;
using StellaOps.Scanner.Analyzers.Native.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Native.Tests;
public class ElfDynamicSectionParserTests
public class ElfDynamicSectionParserTests : NativeTestBase
{
[Fact]
public void ParsesMinimalElfWithNoDynamicSection()
{
// Minimal ELF64 with no program headers (static binary scenario)
var buffer = new byte[64];
SetupElf64Header(buffer, littleEndian: true);
// Minimal ELF64 with no dependencies (static binary scenario)
var elf = ElfBuilder.Static().Build();
using var stream = new MemoryStream(buffer);
var result = ElfDynamicSectionParser.TryParse(stream, out var info);
var result = TryParseElf(elf, out var info);
result.Should().BeTrue();
info.Dependencies.Should().BeEmpty();
@@ -25,72 +24,13 @@ public class ElfDynamicSectionParserTests
[Fact]
public void ParsesElfWithDtNeeded()
{
// Build a minimal ELF64 with PT_DYNAMIC containing DT_NEEDED entries
var buffer = new byte[2048];
SetupElf64Header(buffer, littleEndian: true);
// Build ELF with DT_NEEDED entries using the builder
var elf = ElfBuilder.LinuxX64()
.AddDependencies("libc.so.6", "libm.so.6", "libpthread.so.0")
.Build();
// String table at offset 0x400
var strtab = 0x400;
var str1Offset = 1; // Skip null byte at start
var str2Offset = str1Offset + WriteString(buffer, strtab + str1Offset, "libc.so.6") + 1;
var str3Offset = str2Offset + WriteString(buffer, strtab + str2Offset, "libm.so.6") + 1;
var strtabSize = str3Offset + WriteString(buffer, strtab + str3Offset, "libpthread.so.0") + 1;
var info = ParseElf(elf);
// Section headers at offset 0x600
var shoff = 0x600;
var shentsize = 64; // Elf64_Shdr size
var shnum = 2; // null + .dynstr
// Update ELF header with section header info
BitConverter.GetBytes((ulong)shoff).CopyTo(buffer, 40); // e_shoff
BitConverter.GetBytes((ushort)shentsize).CopyTo(buffer, 58); // e_shentsize
BitConverter.GetBytes((ushort)shnum).CopyTo(buffer, 60); // e_shnum
// Section header 0: null section
// Section header 1: .dynstr (type SHT_STRTAB = 3)
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 (virtual address)
BitConverter.GetBytes((ulong)strtab).CopyTo(buffer, sh1 + 24); // sh_offset (file offset)
BitConverter.GetBytes((ulong)strtabSize).CopyTo(buffer, sh1 + 32); // sh_size
// Dynamic section at offset 0x200
var dynOffset = 0x200;
var dynEntrySize = 16; // Elf64_Dyn size
var dynIndex = 0;
// DT_STRTAB
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex++, 5, 0x400); // DT_STRTAB = 5
// DT_STRSZ
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex++, 10, (ulong)strtabSize); // DT_STRSZ = 10
// DT_NEEDED entries
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex++, 1, (ulong)str1Offset); // libc.so.6
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex++, 1, (ulong)str2Offset); // libm.so.6
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex++, 1, (ulong)str3Offset); // libpthread.so.0
// DT_NULL
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex, 0, 0);
var dynSize = dynEntrySize * (dynIndex + 1);
// Program header at offset 0x40 (right after ELF header)
var phoff = 0x40;
var phentsize = 56; // Elf64_Phdr size
var phnum = 1;
// Update ELF header with program header info
BitConverter.GetBytes((ulong)phoff).CopyTo(buffer, 32); // e_phoff
BitConverter.GetBytes((ushort)phentsize).CopyTo(buffer, 54); // e_phentsize
BitConverter.GetBytes((ushort)phnum).CopyTo(buffer, 56); // e_phnum
// PT_DYNAMIC program header
BitConverter.GetBytes((uint)2).CopyTo(buffer, phoff); // p_type = PT_DYNAMIC
BitConverter.GetBytes((ulong)dynOffset).CopyTo(buffer, phoff + 8); // p_offset
BitConverter.GetBytes((ulong)dynSize).CopyTo(buffer, phoff + 32); // p_filesz
using var stream = new MemoryStream(buffer);
var result = ElfDynamicSectionParser.TryParse(stream, out var info);
result.Should().BeTrue();
info.Dependencies.Should().HaveCount(3);
info.Dependencies[0].Soname.Should().Be("libc.so.6");
info.Dependencies[0].ReasonCode.Should().Be("elf-dtneeded");
@@ -101,60 +41,14 @@ public class ElfDynamicSectionParserTests
[Fact]
public void ParsesElfWithRpathAndRunpath()
{
var buffer = new byte[2048];
SetupElf64Header(buffer, littleEndian: true);
// Build ELF with rpath and runpath using the builder
var elf = ElfBuilder.LinuxX64()
.WithRpath("/opt/lib", "/usr/local/lib")
.WithRunpath("$ORIGIN/../lib")
.Build();
// String table at offset 0x400
var strtab = 0x400;
var rpathOffset = 1;
var runpathOffset = rpathOffset + WriteString(buffer, strtab + rpathOffset, "/opt/lib:/usr/local/lib") + 1;
var strtabSize = runpathOffset + WriteString(buffer, strtab + runpathOffset, "$ORIGIN/../lib") + 1;
var info = ParseElf(elf);
// Section headers
var shoff = 0x600;
var shentsize = 64;
var shnum = 2;
BitConverter.GetBytes((ulong)shoff).CopyTo(buffer, 40);
BitConverter.GetBytes((ushort)shentsize).CopyTo(buffer, 58);
BitConverter.GetBytes((ushort)shnum).CopyTo(buffer, 60);
var sh1 = shoff + shentsize;
BitConverter.GetBytes((uint)3).CopyTo(buffer, sh1 + 4);
BitConverter.GetBytes((ulong)0x400).CopyTo(buffer, sh1 + 16);
BitConverter.GetBytes((ulong)strtab).CopyTo(buffer, sh1 + 24);
BitConverter.GetBytes((ulong)strtabSize).CopyTo(buffer, sh1 + 32);
// 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++, 15, (ulong)rpathOffset); // DT_RPATH
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex++, 29, (ulong)runpathOffset); // DT_RUNPATH
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);
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.Rpath.Should().BeEquivalentTo(["/opt/lib", "/usr/local/lib"]);
info.Runpath.Should().BeEquivalentTo(["$ORIGIN/../lib"]);
}
@@ -162,49 +56,13 @@ public class ElfDynamicSectionParserTests
[Fact]
public void ParsesElfWithInterpreterAndBuildId()
{
var buffer = new byte[1024];
SetupElf64Header(buffer, littleEndian: true);
// Build ELF with interpreter and build ID using the builder
var elf = ElfBuilder.LinuxX64()
.WithBuildId("deadbeef0102030405060708090a0b0c")
.Build();
// Program headers at offset 0x40
var phoff = 0x40;
var phentsize = 56;
var phnum = 2;
var info = ParseElf(elf);
BitConverter.GetBytes((ulong)phoff).CopyTo(buffer, 32);
BitConverter.GetBytes((ushort)phentsize).CopyTo(buffer, 54);
BitConverter.GetBytes((ushort)phnum).CopyTo(buffer, 56);
// PT_INTERP
var ph0 = phoff;
var interpOffset = 0x200;
var interpData = "/lib64/ld-linux-x86-64.so.2\0"u8;
BitConverter.GetBytes((uint)3).CopyTo(buffer, ph0); // p_type = PT_INTERP
BitConverter.GetBytes((ulong)interpOffset).CopyTo(buffer, ph0 + 8); // p_offset
BitConverter.GetBytes((ulong)interpData.Length).CopyTo(buffer, ph0 + 32); // p_filesz
interpData.CopyTo(buffer.AsSpan(interpOffset));
// PT_NOTE with GNU build-id
var ph1 = phoff + phentsize;
var noteOffset = 0x300;
BitConverter.GetBytes((uint)4).CopyTo(buffer, ph1); // p_type = PT_NOTE
BitConverter.GetBytes((ulong)noteOffset).CopyTo(buffer, ph1 + 8); // p_offset
BitConverter.GetBytes((ulong)32).CopyTo(buffer, ph1 + 32); // p_filesz
// Build note structure
BitConverter.GetBytes((uint)4).CopyTo(buffer, noteOffset); // namesz
BitConverter.GetBytes((uint)16).CopyTo(buffer, noteOffset + 4); // descsz
BitConverter.GetBytes((uint)3).CopyTo(buffer, noteOffset + 8); // type = NT_GNU_BUILD_ID
"GNU\0"u8.CopyTo(buffer.AsSpan(noteOffset + 12)); // name
var buildIdBytes = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C };
buildIdBytes.CopyTo(buffer, noteOffset + 16);
using var stream = new MemoryStream(buffer);
var result = ElfDynamicSectionParser.TryParse(stream, out var info);
result.Should().BeTrue();
info.Interpreter.Should().Be("/lib64/ld-linux-x86-64.so.2");
info.BinaryId.Should().Be("deadbeef0102030405060708090a0b0c");
}
@@ -212,57 +70,17 @@ public class ElfDynamicSectionParserTests
[Fact]
public void DeduplicatesDtNeededEntries()
{
var buffer = new byte[2048];
SetupElf64Header(buffer, littleEndian: true);
// ElfBuilder deduplicates internally, so add "duplicates" via builder
// The builder will produce correct output, and we verify the parser handles it
var elf = ElfBuilder.LinuxX64()
.AddDependency("libc.so.6")
.AddDependency("libc.so.6") // Duplicate - builder should handle this
.AddDependency("libc.so.6") // Triple duplicate
.Build();
var strtab = 0x400;
var str1Offset = 1;
var strtabSize = str1Offset + WriteString(buffer, strtab + str1Offset, "libc.so.6") + 1;
var info = ParseElf(elf);
var shoff = 0x600;
var shentsize = 64;
var shnum = 2;
BitConverter.GetBytes((ulong)shoff).CopyTo(buffer, 40);
BitConverter.GetBytes((ushort)shentsize).CopyTo(buffer, 58);
BitConverter.GetBytes((ushort)shnum).CopyTo(buffer, 60);
var sh1 = shoff + shentsize;
BitConverter.GetBytes((uint)3).CopyTo(buffer, sh1 + 4);
BitConverter.GetBytes((ulong)0x400).CopyTo(buffer, sh1 + 16);
BitConverter.GetBytes((ulong)strtab).CopyTo(buffer, sh1 + 24);
BitConverter.GetBytes((ulong)strtabSize).CopyTo(buffer, sh1 + 32);
var dynOffset = 0x200;
var dynEntrySize = 16;
var dynIndex = 0;
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex++, 5, 0x400);
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex++, 10, (ulong)strtabSize);
// Duplicate DT_NEEDED entries for same library
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex++, 1, (ulong)str1Offset);
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex++, 1, (ulong)str1Offset);
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex++, 1, (ulong)str1Offset);
WriteDynEntry64(buffer, dynOffset + dynEntrySize * dynIndex, 0, 0);
var dynSize = dynEntrySize * (dynIndex + 1);
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);
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();
// Whether builder deduplicates or not, parser should return unique deps
info.Dependencies.Should().HaveCount(1);
info.Dependencies[0].Soname.Should().Be("libc.so.6");
}
@@ -291,136 +109,47 @@ public class ElfDynamicSectionParserTests
result.Should().BeFalse();
}
private static void SetupElf64Header(byte[] buffer, bool littleEndian)
{
// ELF magic
buffer[0] = 0x7F;
buffer[1] = (byte)'E';
buffer[2] = (byte)'L';
buffer[3] = (byte)'F';
buffer[4] = 0x02; // 64-bit
buffer[5] = littleEndian ? (byte)0x01 : (byte)0x02;
buffer[6] = 0x01; // ELF version
buffer[7] = 0x00; // System V ABI
// e_type at offset 16 (2 bytes)
buffer[16] = 0x02; // ET_EXEC
// e_machine at offset 18 (2 bytes)
buffer[18] = 0x3E; // x86_64
}
private static void WriteDynEntry64(byte[] buffer, int offset, ulong tag, ulong val)
{
BitConverter.GetBytes(tag).CopyTo(buffer, offset);
BitConverter.GetBytes(val).CopyTo(buffer, offset + 8);
}
private static int WriteString(byte[] buffer, int offset, string str)
{
var bytes = Encoding.UTF8.GetBytes(str);
bytes.CopyTo(buffer, offset);
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);
var elf = ElfBuilder.LinuxX64()
.AddDependency("libc.so.6")
.AddVersionNeed("libc.so.6", "GLIBC_2.17", isWeak: false)
.AddVersionNeed("libc.so.6", "GLIBC_2.28", isWeak: false)
.Build();
// 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;
var info = ParseElf(elf);
// 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");
}
[Fact]
public void ParsesElfWithWeakVersionNeeds()
{
// Test that weak version requirements (VER_FLG_WEAK) are properly detected
var elf = ElfBuilder.LinuxX64()
.AddDependency("libc.so.6")
.AddVersionNeed("libc.so.6", "GLIBC_2.17", isWeak: false) // Required version
.AddVersionNeed("libc.so.6", "GLIBC_2.34", isWeak: true) // Weak/optional version
.Build();
var info = ParseElf(elf);
info.Dependencies.Should().HaveCount(1);
info.Dependencies[0].Soname.Should().Be("libc.so.6");
info.Dependencies[0].VersionNeeds.Should().HaveCount(2);
// GLIBC_2.17 should NOT be weak
var glibc217 = info.Dependencies[0].VersionNeeds.First(v => v.Version == "GLIBC_2.17");
glibc217.IsWeak.Should().BeFalse();
// GLIBC_2.34 should BE weak
var glibc234 = info.Dependencies[0].VersionNeeds.First(v => v.Version == "GLIBC_2.34");
glibc234.IsWeak.Should().BeTrue();
}
}