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
536 lines
17 KiB
C#
536 lines
17 KiB
C#
using FluentAssertions;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.Scanner.Analyzers.Native.Tests;
|
|
|
|
public class ElfResolverTests
|
|
{
|
|
[Fact]
|
|
public void Resolve_WithRpath_FindsLibraryInRpathDirectory()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["/opt/myapp/lib/libfoo.so.1"]);
|
|
var rpaths = new[] { "/opt/myapp/lib" };
|
|
var runpaths = Array.Empty<string>();
|
|
|
|
// Act
|
|
var result = ElfResolver.Resolve("libfoo.so.1", rpaths, runpaths, null, null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("/opt/myapp/lib/libfoo.so.1");
|
|
result.Steps.Should().ContainSingle()
|
|
.Which.Should().Match<ResolveStep>(s =>
|
|
s.SearchPath == "/opt/myapp/lib" &&
|
|
s.SearchReason == "rpath" &&
|
|
s.Found == true);
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_WithRunpath_IgnoresRpath()
|
|
{
|
|
// Arrange - library exists in rpath but not runpath
|
|
var fs = new VirtualFileSystem([
|
|
"/opt/rpath/lib/libfoo.so.1",
|
|
"/opt/runpath/lib/libfoo.so.1"
|
|
]);
|
|
var rpaths = new[] { "/opt/rpath/lib" };
|
|
var runpaths = new[] { "/opt/runpath/lib" };
|
|
|
|
// Act
|
|
var result = ElfResolver.Resolve("libfoo.so.1", rpaths, runpaths, null, null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("/opt/runpath/lib/libfoo.so.1");
|
|
result.Steps.Should().NotContain(s => s.SearchReason == "rpath");
|
|
result.Steps.Should().Contain(s => s.SearchReason == "runpath");
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_WithLdLibraryPath_SearchesBeforeRunpath()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem([
|
|
"/custom/lib/libfoo.so.1",
|
|
"/opt/runpath/lib/libfoo.so.1"
|
|
]);
|
|
var rpaths = Array.Empty<string>();
|
|
var runpaths = new[] { "/opt/runpath/lib" };
|
|
var ldLibraryPath = new[] { "/custom/lib" };
|
|
|
|
// Act
|
|
var result = ElfResolver.Resolve("libfoo.so.1", rpaths, runpaths, ldLibraryPath, null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("/custom/lib/libfoo.so.1");
|
|
result.Steps.First().SearchReason.Should().Be("ld_library_path");
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_WithOriginExpansion_ExpandsOriginVariable()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["/app/bin/../lib/libfoo.so.1"]);
|
|
var rpaths = new[] { "$ORIGIN/../lib" };
|
|
var runpaths = Array.Empty<string>();
|
|
|
|
// Act
|
|
var result = ElfResolver.Resolve("libfoo.so.1", rpaths, runpaths, null, "/app/bin", fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("/app/bin/../lib/libfoo.so.1");
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_WithOriginBraceSyntax_ExpandsOriginVariable()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["/app/bin/../lib/libbar.so.2"]);
|
|
var rpaths = new[] { "${ORIGIN}/../lib" };
|
|
var runpaths = Array.Empty<string>();
|
|
|
|
// Act
|
|
var result = ElfResolver.Resolve("libbar.so.2", rpaths, runpaths, null, "/app/bin", fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("/app/bin/../lib/libbar.so.2");
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_NotFound_ReturnsUnresolvedWithSteps()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem([]);
|
|
var rpaths = new[] { "/opt/lib" };
|
|
var runpaths = Array.Empty<string>();
|
|
|
|
// Act
|
|
var result = ElfResolver.Resolve("libmissing.so.1", rpaths, runpaths, null, null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeFalse();
|
|
result.ResolvedPath.Should().BeNull();
|
|
result.Steps.Should().NotBeEmpty();
|
|
result.Steps.Should().Contain(s => s.SearchReason == "rpath" && !s.Found);
|
|
result.Steps.Should().Contain(s => s.SearchReason == "default" && !s.Found);
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_WithDefaultPaths_SearchesSystemDirectories()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["/usr/lib/libc.so.6"]);
|
|
|
|
// Act
|
|
var result = ElfResolver.Resolve("libc.so.6", [], [], null, null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("/usr/lib/libc.so.6");
|
|
result.Steps.Should().Contain(s => s.SearchReason == "default");
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_SearchOrder_FollowsCorrectPriority()
|
|
{
|
|
// Arrange - library exists in all locations
|
|
var fs = new VirtualFileSystem([
|
|
"/rpath/libfoo.so",
|
|
"/ldpath/libfoo.so",
|
|
"/usr/lib/libfoo.so"
|
|
]);
|
|
var rpaths = new[] { "/rpath" };
|
|
var ldLibraryPath = new[] { "/ldpath" };
|
|
|
|
// Act - no runpath, so rpath should be checked first
|
|
var result = ElfResolver.Resolve("libfoo.so", rpaths, [], ldLibraryPath, null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("/rpath/libfoo.so");
|
|
result.Steps.First().SearchReason.Should().Be("rpath");
|
|
}
|
|
}
|
|
|
|
public class PeResolverTests
|
|
{
|
|
[Fact]
|
|
public void Resolve_InApplicationDirectory_FindsDll()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["C:/MyApp/mylib.dll"]);
|
|
|
|
// Act
|
|
var result = PeResolver.Resolve("mylib.dll", "C:/MyApp", null, null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("C:/MyApp/mylib.dll");
|
|
result.Steps.Should().ContainSingle()
|
|
.Which.SearchReason.Should().Be("application_directory");
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_InSystem32_FindsDll()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["C:/Windows/System32/kernel32.dll"]);
|
|
|
|
// Act
|
|
var result = PeResolver.Resolve("kernel32.dll", "C:/MyApp", null, null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("C:/Windows/System32/kernel32.dll");
|
|
result.Steps.Should().Contain(s => s.SearchReason == "system_directory");
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_InSysWOW64_FindsDll()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["C:/Windows/SysWOW64/wow64.dll"]);
|
|
|
|
// Act
|
|
var result = PeResolver.Resolve("wow64.dll", null, null, null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("C:/Windows/SysWOW64/wow64.dll");
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_InCurrentDirectory_FindsDll()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["C:/WorkDir/plugin.dll"]);
|
|
|
|
// Act
|
|
var result = PeResolver.Resolve("plugin.dll", "C:/MyApp", "C:/WorkDir", null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("C:/WorkDir/plugin.dll");
|
|
result.Steps.Should().Contain(s => s.SearchReason == "current_directory" && s.Found);
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_InPathEnvironment_FindsDll()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["D:/Tools/bin/tool.dll"]);
|
|
var pathEnv = new[] { "D:/Tools/bin", "D:/Other" };
|
|
|
|
// Act
|
|
var result = PeResolver.Resolve("tool.dll", "C:/MyApp", null, pathEnv, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("D:/Tools/bin/tool.dll");
|
|
result.Steps.Should().Contain(s => s.SearchReason == "path_environment" && s.Found);
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_SafeDllSearchOrder_ApplicationBeforeSystem()
|
|
{
|
|
// Arrange - DLL exists in both app dir and system32
|
|
var fs = new VirtualFileSystem([
|
|
"C:/MyApp/common.dll",
|
|
"C:/Windows/System32/common.dll"
|
|
]);
|
|
|
|
// Act
|
|
var result = PeResolver.Resolve("common.dll", "C:/MyApp", null, null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("C:/MyApp/common.dll");
|
|
result.Steps.First().SearchReason.Should().Be("application_directory");
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_NotFound_ReturnsAllSearchedPaths()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem([]);
|
|
var pathEnv = new[] { "D:/Tools" };
|
|
|
|
// Act
|
|
var result = PeResolver.Resolve("missing.dll", "C:/MyApp", "C:/Work", pathEnv, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeFalse();
|
|
result.ResolvedPath.Should().BeNull();
|
|
result.Steps.Should().HaveCountGreaterThan(4);
|
|
result.Steps.Should().Contain(s => s.SearchReason == "application_directory");
|
|
result.Steps.Should().Contain(s => s.SearchReason == "system_directory");
|
|
result.Steps.Should().Contain(s => s.SearchReason == "current_directory");
|
|
result.Steps.Should().Contain(s => s.SearchReason == "path_environment");
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_WithNullApplicationDirectory_SkipsAppDirSearch()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["C:/Windows/System32/test.dll"]);
|
|
|
|
// Act
|
|
var result = PeResolver.Resolve("test.dll", null, null, null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.Steps.Should().NotContain(s => s.SearchReason == "application_directory");
|
|
}
|
|
}
|
|
|
|
public class MachOResolverTests
|
|
{
|
|
[Fact]
|
|
public void Resolve_WithRpath_ExpandsAndFindsLibrary()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["/opt/myapp/Frameworks/libfoo.dylib"]);
|
|
var rpaths = new[] { "/opt/myapp/Frameworks" };
|
|
|
|
// Act
|
|
var result = MachOResolver.Resolve("@rpath/libfoo.dylib", rpaths, null, null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("/opt/myapp/Frameworks/libfoo.dylib");
|
|
result.Steps.Should().ContainSingle()
|
|
.Which.SearchReason.Should().Be("rpath");
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_WithMultipleRpaths_SearchesInOrder()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["/second/path/libfoo.dylib"]);
|
|
var rpaths = new[] { "/first/path", "/second/path" };
|
|
|
|
// Act
|
|
var result = MachOResolver.Resolve("@rpath/libfoo.dylib", rpaths, null, null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("/second/path/libfoo.dylib");
|
|
result.Steps.Should().HaveCount(2);
|
|
result.Steps[0].Found.Should().BeFalse();
|
|
result.Steps[1].Found.Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_WithLoaderPath_ExpandsPlaceholder()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["/app/Contents/MacOS/../Frameworks/libbar.dylib"]);
|
|
|
|
// Act
|
|
var result = MachOResolver.Resolve(
|
|
"@loader_path/../Frameworks/libbar.dylib",
|
|
[],
|
|
"/app/Contents/MacOS",
|
|
null,
|
|
fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("/app/Contents/MacOS/../Frameworks/libbar.dylib");
|
|
result.Steps.Should().ContainSingle()
|
|
.Which.SearchReason.Should().Be("loader_path");
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_WithExecutablePath_ExpandsPlaceholder()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["/Applications/MyApp.app/Contents/MacOS/../Frameworks/lib.dylib"]);
|
|
|
|
// Act
|
|
var result = MachOResolver.Resolve(
|
|
"@executable_path/../Frameworks/lib.dylib",
|
|
[],
|
|
null,
|
|
"/Applications/MyApp.app/Contents/MacOS",
|
|
fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.Steps.Should().ContainSingle()
|
|
.Which.SearchReason.Should().Be("executable_path");
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_WithRpathContainingLoaderPath_ExpandsBoth()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["/app/bin/../lib/libfoo.dylib"]);
|
|
var rpaths = new[] { "@loader_path/../lib" };
|
|
|
|
// Act
|
|
var result = MachOResolver.Resolve("@rpath/libfoo.dylib", rpaths, "/app/bin", null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("/app/bin/../lib/libfoo.dylib");
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_AbsolutePath_ChecksDirectly()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["/usr/lib/libSystem.B.dylib"]);
|
|
|
|
// Act
|
|
var result = MachOResolver.Resolve("/usr/lib/libSystem.B.dylib", [], null, null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("/usr/lib/libSystem.B.dylib");
|
|
result.Steps.Should().ContainSingle()
|
|
.Which.SearchReason.Should().Be("absolute_path");
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_RelativePath_SearchesDefaultPaths()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["/usr/local/lib/libcustom.dylib"]);
|
|
|
|
// Act
|
|
var result = MachOResolver.Resolve("libcustom.dylib", [], null, null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("/usr/local/lib/libcustom.dylib");
|
|
result.Steps.Should().Contain(s => s.SearchReason == "default_library_path");
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_RpathNotFound_FallsBackToDefaultPaths()
|
|
{
|
|
// Arrange - library not in rpath but in default path
|
|
var fs = new VirtualFileSystem(["/usr/lib/libfoo.dylib"]);
|
|
var rpaths = new[] { "/nonexistent/path" };
|
|
|
|
// Act
|
|
var result = MachOResolver.Resolve("@rpath/libfoo.dylib", rpaths, null, null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeTrue();
|
|
result.ResolvedPath.Should().Be("/usr/lib/libfoo.dylib");
|
|
result.Steps.Should().Contain(s => s.SearchReason == "rpath" && !s.Found);
|
|
result.Steps.Should().Contain(s => s.SearchReason == "default_library_path" && s.Found);
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_NotFound_ReturnsAllSearchedPaths()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem([]);
|
|
var rpaths = new[] { "/opt/lib" };
|
|
|
|
// Act
|
|
var result = MachOResolver.Resolve("@rpath/missing.dylib", rpaths, null, null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeFalse();
|
|
result.ResolvedPath.Should().BeNull();
|
|
result.Steps.Should().NotBeEmpty();
|
|
result.Steps.Should().OnlyContain(s => !s.Found);
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_LoaderPathNotFound_ReturnsFalse()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem([]);
|
|
|
|
// Act
|
|
var result = MachOResolver.Resolve("@loader_path/missing.dylib", [], "/app", null, fs);
|
|
|
|
// Assert
|
|
result.Resolved.Should().BeFalse();
|
|
result.Steps.Should().ContainSingle()
|
|
.Which.SearchReason.Should().Be("loader_path");
|
|
}
|
|
}
|
|
|
|
public class VirtualFileSystemTests
|
|
{
|
|
[Fact]
|
|
public void FileExists_WithExistingFile_ReturnsTrue()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["/usr/lib/libc.so.6"]);
|
|
|
|
// Act & Assert
|
|
fs.FileExists("/usr/lib/libc.so.6").Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void FileExists_WithNonExistingFile_ReturnsFalse()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["/usr/lib/libc.so.6"]);
|
|
|
|
// Act & Assert
|
|
fs.FileExists("/usr/lib/missing.so").Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void FileExists_IsCaseInsensitive()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["/USR/LIB/libc.so.6"]);
|
|
|
|
// Act & Assert
|
|
fs.FileExists("/usr/lib/LIBC.SO.6").Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void DirectoryExists_WithExistingDirectory_ReturnsTrue()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["/usr/lib/x86_64-linux-gnu/libc.so.6"]);
|
|
|
|
// Act & Assert
|
|
fs.DirectoryExists("/usr/lib").Should().BeTrue();
|
|
fs.DirectoryExists("/usr/lib/x86_64-linux-gnu").Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void NormalizePath_HandlesBackslashes()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem(["C:/Windows/System32/kernel32.dll"]);
|
|
|
|
// Act & Assert
|
|
fs.FileExists("C:\\Windows\\System32\\kernel32.dll").Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void EnumerateFiles_ReturnsFilesInDirectory()
|
|
{
|
|
// Arrange
|
|
var fs = new VirtualFileSystem([
|
|
"/usr/lib/liba.so",
|
|
"/usr/lib/libb.so",
|
|
"/usr/local/lib/libc.so"
|
|
]);
|
|
|
|
// Act
|
|
var files = fs.EnumerateFiles("/usr/lib", "*").ToList();
|
|
|
|
// Assert
|
|
files.Should().HaveCount(2);
|
|
files.Should().Contain("/usr/lib/liba.so");
|
|
files.Should().Contain("/usr/lib/libb.so");
|
|
}
|
|
}
|