up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
sdk-generator-smoke / sdk-smoke (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
sdk-generator-smoke / sdk-smoke (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,324 @@
|
||||
using System.Text;
|
||||
using FluentAssertions;
|
||||
using StellaOps.Scanner.Analyzers.Native;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Native.Tests;
|
||||
|
||||
public class ElfDynamicSectionParserTests
|
||||
{
|
||||
[Fact]
|
||||
public void ParsesMinimalElfWithNoDynamicSection()
|
||||
{
|
||||
// Minimal ELF64 with no program headers (static binary scenario)
|
||||
var buffer = new byte[64];
|
||||
SetupElf64Header(buffer, littleEndian: true);
|
||||
|
||||
using var stream = new MemoryStream(buffer);
|
||||
var result = ElfDynamicSectionParser.TryParse(stream, out var info);
|
||||
|
||||
result.Should().BeTrue();
|
||||
info.Dependencies.Should().BeEmpty();
|
||||
info.Rpath.Should().BeEmpty();
|
||||
info.Runpath.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParsesElfWithDtNeeded()
|
||||
{
|
||||
// Build a minimal ELF64 with PT_DYNAMIC containing DT_NEEDED entries
|
||||
var buffer = new byte[2048];
|
||||
SetupElf64Header(buffer, littleEndian: true);
|
||||
|
||||
// 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;
|
||||
|
||||
// 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");
|
||||
info.Dependencies[1].Soname.Should().Be("libm.so.6");
|
||||
info.Dependencies[2].Soname.Should().Be("libpthread.so.0");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParsesElfWithRpathAndRunpath()
|
||||
{
|
||||
var buffer = new byte[2048];
|
||||
SetupElf64Header(buffer, littleEndian: true);
|
||||
|
||||
// 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;
|
||||
|
||||
// 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"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParsesElfWithInterpreterAndBuildId()
|
||||
{
|
||||
var buffer = new byte[1024];
|
||||
SetupElf64Header(buffer, littleEndian: true);
|
||||
|
||||
// Program headers at offset 0x40
|
||||
var phoff = 0x40;
|
||||
var phentsize = 56;
|
||||
var phnum = 2;
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeduplicatesDtNeededEntries()
|
||||
{
|
||||
var buffer = new byte[2048];
|
||||
SetupElf64Header(buffer, littleEndian: true);
|
||||
|
||||
var strtab = 0x400;
|
||||
var str1Offset = 1;
|
||||
var strtabSize = str1Offset + WriteString(buffer, strtab + str1Offset, "libc.so.6") + 1;
|
||||
|
||||
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();
|
||||
info.Dependencies.Should().HaveCount(1);
|
||||
info.Dependencies[0].Soname.Should().Be("libc.so.6");
|
||||
}
|
||||
|
||||
[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();
|
||||
}
|
||||
|
||||
[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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user