feat: Implement BerkeleyDB reader for RPM databases
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build 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
console-runner-image / build-runner-image (push) Has been cancelled
wine-csp-build / Build Wine CSP Image (push) Has been cancelled
wine-csp-build / Integration Tests (push) Has been cancelled
wine-csp-build / Security Scan (push) Has been cancelled
wine-csp-build / Generate SBOM (push) Has been cancelled
wine-csp-build / Publish Image (push) Has been cancelled
wine-csp-build / Air-Gap Bundle (push) Has been cancelled
wine-csp-build / Test Summary (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build 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
console-runner-image / build-runner-image (push) Has been cancelled
wine-csp-build / Build Wine CSP Image (push) Has been cancelled
wine-csp-build / Integration Tests (push) Has been cancelled
wine-csp-build / Security Scan (push) Has been cancelled
wine-csp-build / Generate SBOM (push) Has been cancelled
wine-csp-build / Publish Image (push) Has been cancelled
wine-csp-build / Air-Gap Bundle (push) Has been cancelled
wine-csp-build / Test Summary (push) Has been cancelled
- Added BerkeleyDbReader class to read and extract RPM header blobs from BerkeleyDB hash databases. - Implemented methods to detect BerkeleyDB format and extract values, including handling of page sizes and magic numbers. - Added tests for BerkeleyDbReader to ensure correct functionality and header extraction. feat: Add Yarn PnP data tests - Created YarnPnpDataTests to validate package resolution and data loading from Yarn PnP cache. - Implemented tests for resolved keys, package presence, and loading from cache structure. test: Add egg-info package fixtures for Python tests - Created egg-info package fixtures for testing Python analyzers. - Included PKG-INFO, entry_points.txt, and installed-files.txt for comprehensive coverage. test: Enhance RPM database reader tests - Added tests for RpmDatabaseReader to validate fallback to legacy packages when SQLite is missing. - Implemented helper methods to create legacy package files and RPM headers for testing. test: Implement dual signing tests - Added DualSignTests to validate secondary signature addition when configured. - Created stub implementations for crypto providers and key resolvers to facilitate testing. chore: Update CI script for Playwright Chromium installation - Modified ci-console-exports.sh to ensure deterministic Chromium binary installation for console exports tests. - Added checks for Windows compatibility and environment variable setups for Playwright browsers.
This commit is contained in:
@@ -127,6 +127,12 @@ public sealed class BinaryReachabilityLifter : IReachabilityLifter
|
||||
var dependencies = ParseDependencies(data, format, relativePath, cancellationToken, out var observedBuildId);
|
||||
var buildId = observedBuildId ?? identity?.BuildId ?? identity?.Uuid;
|
||||
|
||||
// Detect entry point
|
||||
var entryPoint = DetectEntryPoint(data, format);
|
||||
|
||||
// Collect unknown/unresolved symbols
|
||||
var unknowns = CollectUnknowns(data, format, cancellationToken);
|
||||
|
||||
var symbolId = SymbolId.ForBinaryAddressed(fileHash, ".text", "0x0", Path.GetFileName(path), "static");
|
||||
var codeId = CodeId.ForBinarySegment(format, fileHash, "0x0", data.LongLength, ".text");
|
||||
|
||||
@@ -142,6 +148,13 @@ public sealed class BinaryReachabilityLifter : IReachabilityLifter
|
||||
attributes["build_id"] = buildId!;
|
||||
}
|
||||
|
||||
// Add PURL binding for known library naming conventions
|
||||
var purl = InferPurl(Path.GetFileName(path), format);
|
||||
if (!string.IsNullOrWhiteSpace(purl))
|
||||
{
|
||||
attributes["purl"] = purl;
|
||||
}
|
||||
|
||||
return new BinaryInfo(
|
||||
SymbolId: symbolId,
|
||||
CodeId: codeId,
|
||||
@@ -151,7 +164,9 @@ public sealed class BinaryReachabilityLifter : IReachabilityLifter
|
||||
BuildId: buildId,
|
||||
Dependencies: dependencies,
|
||||
DisplayName: Path.GetFileName(path),
|
||||
Attributes: attributes);
|
||||
Attributes: attributes,
|
||||
EntryPoint: entryPoint,
|
||||
Unknowns: unknowns);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<BinaryDependency> ParseDependencies(
|
||||
@@ -259,6 +274,41 @@ public sealed class BinaryReachabilityLifter : IReachabilityLifter
|
||||
display: info.DisplayName,
|
||||
sourceFile: info.RelativePath,
|
||||
attributes: info.Attributes);
|
||||
|
||||
// Emit synthetic root for entry point
|
||||
if (info.EntryPoint is not null)
|
||||
{
|
||||
var entrySymbolId = SymbolId.ForBinaryAddressed(
|
||||
info.FileHash,
|
||||
".text",
|
||||
info.EntryPoint.Address,
|
||||
info.EntryPoint.Name,
|
||||
"entry");
|
||||
|
||||
var entryAttributes = new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["kind"] = "entry_point",
|
||||
["is_synthetic_root"] = "true"
|
||||
};
|
||||
|
||||
builder.AddNode(
|
||||
symbolId: entrySymbolId,
|
||||
lang: SymbolId.Lang.Binary,
|
||||
kind: "entry_point",
|
||||
display: info.EntryPoint.Name,
|
||||
sourceFile: info.RelativePath,
|
||||
attributes: entryAttributes);
|
||||
|
||||
// Edge from entry point to binary root
|
||||
builder.AddEdge(
|
||||
from: entrySymbolId,
|
||||
to: info.SymbolId,
|
||||
edgeType: EdgeTypes.Call,
|
||||
confidence: EdgeConfidence.Certain,
|
||||
origin: "static",
|
||||
provenance: "elf-entry",
|
||||
evidence: $"file:{info.RelativePath}:entry");
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitDependencies(ReachabilityGraphBuilder builder, BinaryInfo info)
|
||||
@@ -277,6 +327,13 @@ public sealed class BinaryReachabilityLifter : IReachabilityLifter
|
||||
["reason"] = dep.Reason
|
||||
};
|
||||
|
||||
// Add PURL for dependency if inferrable
|
||||
var depPurl = InferPurl(dep.Name, info.Format);
|
||||
if (!string.IsNullOrWhiteSpace(depPurl))
|
||||
{
|
||||
depAttributes["purl"] = depPurl;
|
||||
}
|
||||
|
||||
builder.AddNode(
|
||||
symbolId: depSymbolId,
|
||||
lang: SymbolId.Lang.Binary,
|
||||
@@ -293,6 +350,211 @@ public sealed class BinaryReachabilityLifter : IReachabilityLifter
|
||||
provenance: dep.Provenance,
|
||||
evidence: dep.Evidence);
|
||||
}
|
||||
|
||||
// Emit unknown/unresolved symbols
|
||||
EmitUnknowns(builder, info);
|
||||
}
|
||||
|
||||
private static void EmitUnknowns(ReachabilityGraphBuilder builder, BinaryInfo info)
|
||||
{
|
||||
if (info.Unknowns.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var unknown in info.Unknowns)
|
||||
{
|
||||
var unknownSymbolId = SymbolId.ForBinaryAddressed(
|
||||
info.FileHash,
|
||||
".undef",
|
||||
"0x0",
|
||||
unknown.SymbolName,
|
||||
"undefined");
|
||||
|
||||
var unknownAttributes = new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["is_unknown"] = "true",
|
||||
["reason"] = unknown.ReasonCode
|
||||
};
|
||||
|
||||
builder.AddNode(
|
||||
symbolId: unknownSymbolId,
|
||||
lang: SymbolId.Lang.Binary,
|
||||
kind: "unknown",
|
||||
display: $"?{unknown.SymbolName}",
|
||||
attributes: unknownAttributes);
|
||||
|
||||
builder.AddEdge(
|
||||
from: info.SymbolId,
|
||||
to: unknownSymbolId,
|
||||
edgeType: EdgeTypes.Call,
|
||||
confidence: EdgeConfidence.Medium,
|
||||
origin: "static",
|
||||
provenance: "symbol-undef",
|
||||
evidence: $"file:{info.RelativePath}:undef");
|
||||
}
|
||||
}
|
||||
|
||||
private static BinaryEntryPoint? DetectEntryPoint(byte[] data, string format)
|
||||
{
|
||||
if (data.Length < 64)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case "elf":
|
||||
return DetectElfEntryPoint(data);
|
||||
case "pe":
|
||||
return DetectPeEntryPoint(data);
|
||||
case "macho":
|
||||
return DetectMachOEntryPoint(data);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static BinaryEntryPoint? DetectElfEntryPoint(byte[] data)
|
||||
{
|
||||
if (data.Length < 32)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var is64Bit = data[4] == 2;
|
||||
var isBigEndian = data[5] == 2;
|
||||
|
||||
ulong entryAddr;
|
||||
if (is64Bit)
|
||||
{
|
||||
entryAddr = isBigEndian
|
||||
? System.Buffers.Binary.BinaryPrimitives.ReadUInt64BigEndian(data.AsSpan(24, 8))
|
||||
: System.Buffers.Binary.BinaryPrimitives.ReadUInt64LittleEndian(data.AsSpan(24, 8));
|
||||
}
|
||||
else
|
||||
{
|
||||
entryAddr = isBigEndian
|
||||
? System.Buffers.Binary.BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(24, 4))
|
||||
: System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(data.AsSpan(24, 4));
|
||||
}
|
||||
|
||||
if (entryAddr == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BinaryEntryPoint("_start", $"0x{entryAddr:x}");
|
||||
}
|
||||
|
||||
private static BinaryEntryPoint? DetectPeEntryPoint(byte[] data)
|
||||
{
|
||||
if (data.Length < 0x40)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var peHeaderOffset = System.Buffers.Binary.BinaryPrimitives.ReadInt32LittleEndian(data.AsSpan(0x3C, 4));
|
||||
if (peHeaderOffset < 0 || peHeaderOffset + 0x28 > data.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// PE optional header AddressOfEntryPoint is at offset 16 from optional header start (pe+24)
|
||||
var optionalHeaderOffset = peHeaderOffset + 24;
|
||||
if (optionalHeaderOffset + 20 > data.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var entryPointRva = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(
|
||||
data.AsSpan(optionalHeaderOffset + 16, 4));
|
||||
|
||||
if (entryPointRva == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BinaryEntryPoint("_mainCRTStartup", $"0x{entryPointRva:x}");
|
||||
}
|
||||
|
||||
private static BinaryEntryPoint? DetectMachOEntryPoint(byte[] data)
|
||||
{
|
||||
if (data.Length < 32)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var magic = System.Buffers.Binary.BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(0, 4));
|
||||
var is64 = magic is 0xFEEDFACF or 0xCFFAEDFE;
|
||||
var isBigEndian = magic is 0xFEEDFACE or 0xFEEDFACF;
|
||||
|
||||
// For Mach-O, entry point is in LC_MAIN load command
|
||||
// For simplicity, we'll return a synthetic entry point
|
||||
// Full parsing would require walking load commands
|
||||
return new BinaryEntryPoint("main", "0x0");
|
||||
}
|
||||
|
||||
private static IReadOnlyList<BinaryUnknown> CollectUnknowns(byte[] data, string format, CancellationToken cancellationToken)
|
||||
{
|
||||
// For now, return empty list - full implementation would parse symbol tables
|
||||
// for undefined symbols (STT_NOTYPE with SHN_UNDEF in ELF, etc.)
|
||||
// This is a placeholder for the baseline; full implementation would require
|
||||
// parsing the symbol tables of ELF/PE/Mach-O files
|
||||
return Array.Empty<BinaryUnknown>();
|
||||
}
|
||||
|
||||
private static string? InferPurl(string fileName, string format)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(fileName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract library name and version from common naming patterns
|
||||
// ELF: libfoo.so.1.2.3 -> pkg:generic/libfoo
|
||||
// PE: foo.dll -> pkg:generic/foo
|
||||
// Mach-O: libfoo.dylib -> pkg:generic/libfoo
|
||||
|
||||
string? name = null;
|
||||
string? version = null;
|
||||
|
||||
if (format == "elf" && fileName.Contains(".so"))
|
||||
{
|
||||
// libssl.so.3 or libcrypto.so.1.1
|
||||
var soIndex = fileName.IndexOf(".so", StringComparison.Ordinal);
|
||||
if (soIndex > 0)
|
||||
{
|
||||
name = fileName[..soIndex];
|
||||
var afterSo = fileName[(soIndex + 3)..];
|
||||
if (afterSo.StartsWith('.') && afterSo.Length > 1)
|
||||
{
|
||||
version = afterSo[1..];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (format == "pe" && fileName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
name = fileName[..^4]; // Remove .dll
|
||||
}
|
||||
else if (format == "macho" && fileName.EndsWith(".dylib", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
name = fileName[..^6]; // Remove .dylib
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Build PURL - pkg:generic/name@version
|
||||
var purl = $"pkg:generic/{Uri.EscapeDataString(name)}";
|
||||
if (!string.IsNullOrWhiteSpace(version))
|
||||
{
|
||||
purl += $"@{Uri.EscapeDataString(version)}";
|
||||
}
|
||||
|
||||
return purl;
|
||||
}
|
||||
|
||||
private static bool TryDetect(byte[] data, out NativeBinaryIdentity identity)
|
||||
@@ -330,7 +592,9 @@ public sealed class BinaryReachabilityLifter : IReachabilityLifter
|
||||
string? BuildId,
|
||||
IReadOnlyList<BinaryDependency> Dependencies,
|
||||
string DisplayName,
|
||||
IReadOnlyDictionary<string, string> Attributes);
|
||||
IReadOnlyDictionary<string, string> Attributes,
|
||||
BinaryEntryPoint? EntryPoint,
|
||||
IReadOnlyList<BinaryUnknown> Unknowns);
|
||||
|
||||
private sealed record BinaryDependency(
|
||||
string Name,
|
||||
@@ -338,4 +602,12 @@ public sealed class BinaryReachabilityLifter : IReachabilityLifter
|
||||
EdgeConfidence Confidence,
|
||||
string Provenance,
|
||||
string Evidence);
|
||||
|
||||
private sealed record BinaryEntryPoint(
|
||||
string Name,
|
||||
string Address);
|
||||
|
||||
private sealed record BinaryUnknown(
|
||||
string SymbolName,
|
||||
string ReasonCode);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user