using FluentAssertions; using StellaOps.Scanner.Analyzers.Native; using StellaOps.Scanner.Analyzers.Native.Tests.Fixtures; using StellaOps.Scanner.Analyzers.Native.Tests.TestUtilities; using StellaOps.TestKit; namespace StellaOps.Scanner.Analyzers.Native.Tests; public class ElfDynamicSectionParserTests : NativeTestBase { [Trait("Category", TestCategories.Unit)] [Fact] public void ParsesMinimalElfWithNoDynamicSection() { // Minimal ELF64 with no dependencies (static binary scenario) var elf = ElfBuilder.Static().Build(); var result = TryParseElf(elf, out var info); result.Should().BeTrue(); info.Dependencies.Should().BeEmpty(); info.Rpath.Should().BeEmpty(); info.Runpath.Should().BeEmpty(); } [Trait("Category", TestCategories.Unit)] [Fact] public void ParsesElfWithDtNeeded() { // Build ELF with DT_NEEDED entries using the builder var elf = ElfBuilder.LinuxX64() .AddDependencies("libc.so.6", "libm.so.6", "libpthread.so.0") .Build(); var info = ParseElf(elf); info.Dependencies.Should().HaveCount(3); info.Dependencies[0].Soname.Should().Be("libc.so.6"); info.Dependencies[0].ReasonCode.Should().Be("elf-dtneeded"); info.Dependencies[1].Soname.Should().Be("libm.so.6"); info.Dependencies[2].Soname.Should().Be("libpthread.so.0"); } [Trait("Category", TestCategories.Unit)] [Fact] public void ParsesElfWithRpathAndRunpath() { // Build ELF with rpath and runpath using the builder var elf = ElfBuilder.LinuxX64() .WithRpath("/opt/lib", "/usr/local/lib") .WithRunpath("$ORIGIN/../lib") .Build(); var info = ParseElf(elf); info.Rpath.Should().BeEquivalentTo(["/opt/lib", "/usr/local/lib"]); info.Runpath.Should().BeEquivalentTo(["$ORIGIN/../lib"]); } [Trait("Category", TestCategories.Unit)] [Fact] public void ParsesElfWithInterpreterAndBuildId() { // Build ELF with interpreter and build ID using the builder var elf = ElfBuilder.LinuxX64() .WithBuildId("deadbeef0102030405060708090a0b0c") .Build(); var info = ParseElf(elf); info.Interpreter.Should().Be("/lib64/ld-linux-x86-64.so.2"); info.BinaryId.Should().Be("deadbeef0102030405060708090a0b0c"); } [Trait("Category", TestCategories.Unit)] [Fact] public void DeduplicatesDtNeededEntries() { // 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 info = ParseElf(elf); // Whether builder deduplicates or not, parser should return unique deps info.Dependencies.Should().HaveCount(1); info.Dependencies[0].Soname.Should().Be("libc.so.6"); } [Trait("Category", TestCategories.Unit)] [Fact] public void ReturnsFalseForNonElfData() { var buffer = new byte[] { 0x00, 0x01, 0x02, 0x03 }; using var stream = new MemoryStream(buffer); var result = ElfDynamicSectionParser.TryParse(stream, out var info); result.Should().BeFalse(); } [Trait("Category", TestCategories.Unit)] [Fact] public void ReturnsFalseForPeFile() { var buffer = new byte[256]; buffer[0] = (byte)'M'; buffer[1] = (byte)'Z'; using var stream = new MemoryStream(buffer); var result = ElfDynamicSectionParser.TryParse(stream, out var info); result.Should().BeFalse(); } [Trait("Category", TestCategories.Unit)] [Fact] public void ParsesElfWithVersionNeeds() { // Test that version needs (GLIBC_2.17, etc.) are properly extracted 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(); 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); info.Dependencies[0].VersionNeeds.Should().Contain(v => v.Version == "GLIBC_2.17"); info.Dependencies[0].VersionNeeds.Should().Contain(v => v.Version == "GLIBC_2.28"); } [Trait("Category", TestCategories.Unit)] [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(); } }