up
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using StellaOps.Scanner.Analyzers.Native.Observations;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Native.Reachability;
|
||||
|
||||
/// <summary>
|
||||
/// Builds a deterministic reachability graph from native observations.
|
||||
/// </summary>
|
||||
internal static class NativeReachabilityGraphBuilder
|
||||
{
|
||||
private const string PayloadType = "stellaops.native.graph@1";
|
||||
|
||||
public static NativeReachabilityGraph Build(NativeObservationDocument document, string? layerDigest = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(document);
|
||||
ArgumentNullException.ThrowIfNull(document.Binary);
|
||||
|
||||
var nodes = new List<NativeReachabilityNode>();
|
||||
var edges = new List<NativeReachabilityEdge>();
|
||||
|
||||
var binaryId = ResolveBinaryId(document);
|
||||
var codeId = ResolveCodeId(document);
|
||||
var binaryNodeId = $"bin::{binaryId}";
|
||||
var rootNodeId = $"root::{binaryId}";
|
||||
|
||||
// Root node to capture synthetic sources (init arrays, entrypoints).
|
||||
nodes.Add(new NativeReachabilityNode(
|
||||
Id: rootNodeId,
|
||||
Kind: "root",
|
||||
Name: document.Binary.Path ?? "native-binary",
|
||||
Path: document.Binary.Path,
|
||||
BuildId: document.Binary.BuildId,
|
||||
CodeId: codeId,
|
||||
Format: document.Binary.Format,
|
||||
Architecture: document.Binary.Architecture));
|
||||
|
||||
nodes.Add(new NativeReachabilityNode(
|
||||
Id: binaryNodeId,
|
||||
Kind: "binary",
|
||||
Name: document.Binary.Path ?? "native-binary",
|
||||
Path: document.Binary.Path,
|
||||
BuildId: document.Binary.BuildId,
|
||||
CodeId: codeId,
|
||||
Format: document.Binary.Format,
|
||||
Architecture: document.Binary.Architecture));
|
||||
|
||||
edges.Add(new NativeReachabilityEdge(rootNodeId, binaryNodeId, "binary"));
|
||||
|
||||
// Entrypoints -> binary (synthetic roots)
|
||||
foreach (var entry in document.Entrypoints ?? Array.Empty<NativeObservationEntrypoint>())
|
||||
{
|
||||
var entryId = $"entry::{entry.Symbol ?? entry.Type ?? "entry"}";
|
||||
nodes.Add(new NativeReachabilityNode(
|
||||
Id: entryId,
|
||||
Kind: "entrypoint",
|
||||
Name: entry.Symbol ?? entry.Type ?? "entry",
|
||||
Path: document.Binary.Path,
|
||||
BuildId: document.Binary.BuildId,
|
||||
CodeId: codeId,
|
||||
Format: document.Binary.Format,
|
||||
Architecture: document.Binary.Architecture));
|
||||
|
||||
edges.Add(new NativeReachabilityEdge(rootNodeId, entryId, "entrypoint"));
|
||||
edges.Add(new NativeReachabilityEdge(entryId, binaryNodeId, "entrypoint"));
|
||||
}
|
||||
|
||||
// Declared dependencies -> binary
|
||||
foreach (var dep in document.DeclaredEdges ?? Array.Empty<NativeObservationDeclaredEdge>())
|
||||
{
|
||||
var targetId = $"decl::{dep.Target}";
|
||||
nodes.Add(new NativeReachabilityNode(
|
||||
Id: targetId,
|
||||
Kind: "dependency",
|
||||
Name: dep.Target ?? "unknown",
|
||||
BuildId: null,
|
||||
CodeId: null,
|
||||
Format: null,
|
||||
Path: dep.Target));
|
||||
|
||||
edges.Add(new NativeReachabilityEdge(binaryNodeId, targetId, dep.Reason ?? "declared"));
|
||||
}
|
||||
|
||||
// Heuristic/runtime edges as unknown targets
|
||||
foreach (var edge in document.HeuristicEdges ?? Array.Empty<NativeObservationHeuristicEdge>())
|
||||
{
|
||||
var targetId = $"heur::{edge.Target}";
|
||||
nodes.Add(new NativeReachabilityNode(
|
||||
Id: targetId,
|
||||
Kind: "dependency",
|
||||
Name: edge.Target ?? "unknown",
|
||||
Path: edge.Target));
|
||||
|
||||
edges.Add(new NativeReachabilityEdge(binaryNodeId, targetId, edge.Reason ?? "heuristic"));
|
||||
}
|
||||
|
||||
foreach (var edge in document.RuntimeEdges ?? Array.Empty<NativeObservationRuntimeEdge>())
|
||||
{
|
||||
var targetId = $"rt::{edge.Target}";
|
||||
nodes.Add(new NativeReachabilityNode(
|
||||
Id: targetId,
|
||||
Kind: "runtime",
|
||||
Name: edge.Target ?? "runtime",
|
||||
Path: edge.Target));
|
||||
|
||||
edges.Add(new NativeReachabilityEdge(binaryNodeId, targetId, edge.Reason ?? "runtime"));
|
||||
}
|
||||
|
||||
var distinctNodes = nodes
|
||||
.GroupBy(n => n.Id, StringComparer.Ordinal)
|
||||
.Select(g => g.First())
|
||||
.OrderBy(n => n.Id, StringComparer.Ordinal)
|
||||
.ToImmutableArray();
|
||||
|
||||
var distinctEdges = edges
|
||||
.GroupBy(e => (e.From, e.To, e.Reason), ValueTuple.Create)
|
||||
.Select(g => g.First())
|
||||
.OrderBy(e => e.From, StringComparer.Ordinal)
|
||||
.ThenBy(e => e.To, StringComparer.Ordinal)
|
||||
.ThenBy(e => e.Reason, StringComparer.Ordinal)
|
||||
.ToImmutableArray();
|
||||
|
||||
return new NativeReachabilityGraph(distinctNodes, distinctEdges, layerDigest, document.Binary.BuildId, codeId);
|
||||
}
|
||||
|
||||
public static NativeReachabilityBundle ToBundle(NativeObservationDocument document, string? layerDigest = null)
|
||||
{
|
||||
var graph = Build(document, layerDigest);
|
||||
return new NativeReachabilityBundle(
|
||||
PayloadType,
|
||||
graph,
|
||||
layerDigest,
|
||||
graph.BuildId,
|
||||
graph.CodeId);
|
||||
}
|
||||
|
||||
private static string ResolveBinaryId(NativeObservationDocument document)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(document.Binary.BuildId))
|
||||
{
|
||||
return $"buildid:{document.Binary.BuildId}";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(document.Binary.Sha256))
|
||||
{
|
||||
return $"sha256:{document.Binary.Sha256}";
|
||||
}
|
||||
|
||||
return $"path:{document.Binary.Path}";
|
||||
}
|
||||
|
||||
private static string? ResolveCodeId(NativeObservationDocument document)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(document.Binary.BuildId))
|
||||
{
|
||||
return document.Binary.BuildId;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(document.Binary.Sha256))
|
||||
{
|
||||
return document.Binary.Sha256;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(document.Binary.Path))
|
||||
{
|
||||
return ComputeSha256(document.Binary.Path);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string ComputeSha256(string value)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(value);
|
||||
var hash = SHA256.HashData(bytes);
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Native.Reachability;
|
||||
|
||||
internal sealed record NativeReachabilityNode(
|
||||
string Id,
|
||||
string Kind,
|
||||
string Name,
|
||||
string? BuildId = null,
|
||||
string? CodeId = null,
|
||||
string? Format = null,
|
||||
string? Path = null,
|
||||
string? Architecture = null);
|
||||
|
||||
internal sealed record NativeReachabilityEdge(
|
||||
string From,
|
||||
string To,
|
||||
string Reason);
|
||||
|
||||
internal sealed record NativeReachabilityGraph(
|
||||
ImmutableArray<NativeReachabilityNode> Nodes,
|
||||
ImmutableArray<NativeReachabilityEdge> Edges,
|
||||
string? LayerDigest,
|
||||
string? BuildId,
|
||||
string? CodeId);
|
||||
|
||||
internal sealed record NativeReachabilityBundle(
|
||||
string PayloadType,
|
||||
NativeReachabilityGraph Graph,
|
||||
string? LayerDigest,
|
||||
string? BuildId,
|
||||
string? CodeId);
|
||||
Reference in New Issue
Block a user