feat: Add initial implementation of Vulnerability Resolver Jobs
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Created project for StellaOps.Scanner.Analyzers.Native.Tests with necessary dependencies.
- Documented roles and guidelines in AGENTS.md for Scheduler module.
- Implemented IResolverJobService interface and InMemoryResolverJobService for handling resolver jobs.
- Added ResolverBacklogNotifier and ResolverBacklogService for monitoring job metrics.
- Developed API endpoints for managing resolver jobs and retrieving metrics.
- Defined models for resolver job requests and responses.
- Integrated dependency injection for resolver job services.
- Implemented ImpactIndexSnapshot for persisting impact index data.
- Introduced SignalsScoringOptions for configurable scoring weights in reachability scoring.
- Added unit tests for ReachabilityScoringService and RuntimeFactsIngestionService.
- Created dotnet-filter.sh script to handle command-line arguments for dotnet.
- Established nuget-prime project for managing package downloads.
This commit is contained in:
master
2025-11-18 07:52:15 +02:00
parent e69b57d467
commit 8355e2ff75
299 changed files with 13293 additions and 2444 deletions

View File

@@ -0,0 +1,4 @@
root = false
[*.cs]
dotnet_diagnostic.CA2022.severity = none

View File

@@ -0,0 +1,10 @@
namespace StellaOps.Scanner.Analyzers.Native;
public sealed record NativeBinaryIdentity(
NativeFormat Format,
string? CpuArchitecture,
string? OperatingSystem,
string? Endianness,
string? BuildId,
string? Uuid,
string? InterpreterPath);

View File

@@ -0,0 +1,9 @@
namespace StellaOps.Scanner.Analyzers.Native;
public enum NativeFormat
{
Unknown = 0,
Elf = 1,
Pe = 2,
MachO = 3,
}

View File

@@ -0,0 +1,374 @@
using System;
using System.Buffers.Binary;
using System.IO;
#pragma warning disable CA2022 // Stream.Read validation handled via ReadExactly/ReadAtLeast
using System.Text;
namespace StellaOps.Scanner.Analyzers.Native;
public static class NativeFormatDetector
{
public static bool TryDetect(Stream stream, out NativeBinaryIdentity identity, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(stream);
Span<byte> header = stackalloc byte[512];
var read = stream.ReadAtLeast(header, 4, throwOnEndOfStream: false);
if (read < 4)
{
identity = new NativeBinaryIdentity(NativeFormat.Unknown, null, null, null, null, null, null);
return false;
}
var span = header[..read];
if (IsElf(span, stream, out identity))
{
return true;
}
if (IsPe(span, out identity))
{
return true;
}
if (IsMachO(span, stream, out identity))
{
return true;
}
identity = new NativeBinaryIdentity(NativeFormat.Unknown, null, null, null, null, null, null);
return false;
}
private static bool IsElf(ReadOnlySpan<byte> span, Stream stream, out NativeBinaryIdentity identity)
{
identity = default!;
if (span.Length < 20)
{
return false;
}
if (span[0] != 0x7F || span[1] != (byte)'E' || span[2] != (byte)'L' || span[3] != (byte)'F')
{
return false;
}
var elfClass = span[4]; // 1=32,2=64
var dataEncoding = span[5]; // 1=LE,2=BE
var osAbi = span[7];
var endianness = dataEncoding == 2 ? "be" : "le";
ushort machine;
if (dataEncoding == 2)
{
machine = BinaryPrimitives.ReadUInt16BigEndian(span.Slice(18, 2));
}
else
{
machine = BinaryPrimitives.ReadUInt16LittleEndian(span.Slice(18, 2));
}
var arch = MapElfMachine(machine);
var os = MapElfOs(osAbi);
var phoff = dataEncoding == 2
? BinaryPrimitives.ReadUInt64BigEndian(span.Slice(32, 8))
: BinaryPrimitives.ReadUInt64LittleEndian(span.Slice(32, 8));
var phentsize = dataEncoding == 2
? BinaryPrimitives.ReadUInt16BigEndian(span.Slice(54, 2))
: BinaryPrimitives.ReadUInt16LittleEndian(span.Slice(54, 2));
var phnum = dataEncoding == 2
? BinaryPrimitives.ReadUInt16BigEndian(span.Slice(56, 2))
: BinaryPrimitives.ReadUInt16LittleEndian(span.Slice(56, 2));
string? buildId = null;
string? interp = null;
if (phentsize > 0 && phnum > 0 && phoff > 0)
{
for (var i = 0; i < phnum; i++)
{
var entryOffset = (long)(phoff + (ulong)(i * phentsize));
if (entryOffset + phentsize > stream.Length)
{
break;
}
var ph = new byte[phentsize];
stream.Seek(entryOffset, SeekOrigin.Begin);
stream.ReadExactly(ph, 0, ph.Length);
var phSpan = ph.AsSpan();
var pType = dataEncoding == 2
? BinaryPrimitives.ReadUInt32BigEndian(phSpan)
: BinaryPrimitives.ReadUInt32LittleEndian(phSpan);
if (pType == 3 && interp is null) // PT_INTERP
{
ulong offset = dataEncoding == 2
? BinaryPrimitives.ReadUInt64BigEndian(phSpan.Slice(8, 8))
: BinaryPrimitives.ReadUInt64LittleEndian(phSpan.Slice(8, 8));
uint fileSize = dataEncoding == 2
? BinaryPrimitives.ReadUInt32BigEndian(phSpan.Slice(32, 4))
: BinaryPrimitives.ReadUInt32LittleEndian(phSpan.Slice(32, 4));
if (fileSize > 0 && offset + fileSize <= (ulong)stream.Length)
{
var buffer = new byte[fileSize];
stream.Seek((long)offset, SeekOrigin.Begin);
stream.ReadExactly(buffer, 0, buffer.Length);
var str = System.Text.Encoding.ASCII.GetString(buffer).TrimEnd('\0');
if (!string.IsNullOrWhiteSpace(str))
{
interp = str;
}
}
}
else if (pType == 4 && buildId is null) // PT_NOTE
{
ulong offset = dataEncoding == 2
? BinaryPrimitives.ReadUInt64BigEndian(phSpan.Slice(8, 8))
: BinaryPrimitives.ReadUInt64LittleEndian(phSpan.Slice(8, 8));
uint fileSize = dataEncoding == 2
? BinaryPrimitives.ReadUInt32BigEndian(phSpan.Slice(32, 4))
: BinaryPrimitives.ReadUInt32LittleEndian(phSpan.Slice(32, 4));
if (fileSize > 0 && offset + fileSize <= (ulong)stream.Length)
{
var buffer = new byte[fileSize];
stream.Seek((long)offset, SeekOrigin.Begin);
stream.ReadExactly(buffer, 0, buffer.Length);
ParseElfNote(buffer, dataEncoding == 2, ref buildId);
}
}
if (buildId is not null && interp is not null)
{
break;
}
}
}
identity = new NativeBinaryIdentity(NativeFormat.Elf, arch, os, endianness, buildId, null, interp);
return true;
}
private static bool IsPe(ReadOnlySpan<byte> span, out NativeBinaryIdentity identity)
{
identity = default!;
if (span.Length < 0x40)
{
return false;
}
if (span[0] != 'M' || span[1] != 'Z')
{
return false;
}
var peHeaderOffset = BinaryPrimitives.ReadInt32LittleEndian(span.Slice(0x3C, 4));
if (peHeaderOffset < 0 || peHeaderOffset + 6 > span.Length)
{
return false;
}
if (peHeaderOffset + 6 > span.Length)
{
identity = new NativeBinaryIdentity(NativeFormat.Unknown, null, null, null, null, null, null);
return false;
}
if (span[peHeaderOffset] != 'P' || span[peHeaderOffset + 1] != 'E' || span[peHeaderOffset + 2] != 0 || span[peHeaderOffset + 3] != 0)
{
return false;
}
var machine = BinaryPrimitives.ReadUInt16LittleEndian(span.Slice(peHeaderOffset + 4, 2));
var arch = MapPeMachine(machine);
identity = new NativeBinaryIdentity(NativeFormat.Pe, arch, "windows", Endianness: "le", BuildId: null, Uuid: null, InterpreterPath: null);
return true;
}
private static bool IsMachO(ReadOnlySpan<byte> span, Stream stream, out NativeBinaryIdentity identity)
{
identity = default!;
if (span.Length < 12)
{
return false;
}
var magic = BinaryPrimitives.ReadUInt32BigEndian(span);
var isFat = magic is 0xCAFEBABE or 0xBEBAFECA;
var is64 = magic is 0xFEEDFACF or 0xCFFAEDFE;
var is32 = magic is 0xFEEDFACE or 0xCEFAEDFE;
if (!(isFat || is64 || is32))
{
return false;
}
bool bigEndian = magic is 0xCAFEBABE or 0xFEEDFACE or 0xFEEDFACF;
uint cputype;
if (isFat)
{
var cputypeOffset = 8; // first architecture entry
if (span.Length < cputypeOffset + 4)
{
identity = new NativeBinaryIdentity(NativeFormat.Unknown, null, null, null, null, null, null);
return false;
}
cputype = bigEndian
? BinaryPrimitives.ReadUInt32BigEndian(span.Slice(cputypeOffset, 4))
: BinaryPrimitives.ReadUInt32LittleEndian(span.Slice(cputypeOffset, 4));
}
else
{
cputype = bigEndian
? BinaryPrimitives.ReadUInt32BigEndian(span.Slice(4, 4))
: BinaryPrimitives.ReadUInt32LittleEndian(span.Slice(4, 4));
}
var arch = MapMachCpuType(cputype);
var endianness = bigEndian ? "be" : "le";
var uuid = ExtractMachUuid(stream, bigEndian);
identity = new NativeBinaryIdentity(NativeFormat.MachO, arch, "darwin", Endianness: endianness, BuildId: null, Uuid: uuid, InterpreterPath: null);
return true;
}
private static string? ExtractMachUuid(Stream stream, bool bigEndian)
{
try
{
stream.Seek(0, SeekOrigin.Begin);
using var reader = new BinaryReader(stream, System.Text.Encoding.ASCII, leaveOpen: true);
var magic = reader.ReadUInt32();
var is64 = magic is 0xFEEDFACF or 0xCFFAEDFE;
var headerSize = is64 ? 32 : 28;
stream.Seek(16, SeekOrigin.Begin);
var ncmds = ReadUInt32(reader, bigEndian);
_ = ReadUInt32(reader, bigEndian); // sizeofcmds
stream.Seek(headerSize, SeekOrigin.Begin);
for (var i = 0; i < ncmds; i++)
{
var cmdStart = stream.Position;
var cmd = ReadUInt32(reader, bigEndian);
var cmdsize = ReadUInt32(reader, bigEndian);
if (cmd == 0x1B) // LC_UUID
{
var uuidBytes = reader.ReadBytes(16);
return new Guid(uuidBytes).ToString();
}
stream.Seek(cmdStart + cmdsize, SeekOrigin.Begin);
}
}
catch
{
return null;
}
return null;
}
private static uint ReadUInt32(BinaryReader reader, bool bigEndian)
{
var data = reader.ReadBytes(4);
return bigEndian ? BinaryPrimitives.ReadUInt32BigEndian(data) : BinaryPrimitives.ReadUInt32LittleEndian(data);
}
private static string? MapElfMachine(ushort machine) => machine switch
{
0x03 => "x86",
0x08 => "mips",
0x14 => "powerpc",
0x28 => "arm",
0x32 => "ia64",
0x3E => "x86_64",
0xB7 => "aarch64",
_ => null,
};
private static string? MapElfOs(byte abi) => abi switch
{
0x00 => "linux",
0x03 => "linux",
0x06 => "solaris",
0x07 => "aix",
0x08 => "irix",
0x09 => "freebsd",
0x0C => "openbsd",
_ => null,
};
private static string? MapPeMachine(ushort machine) => machine switch
{
0x014c => "x86",
0x0200 => "ia64",
0x8664 => "x86_64",
0x01c0 => "arm",
0x01c4 => "armv7",
0xAA64 => "arm64",
_ => null,
};
private static string? MapMachCpuType(uint cpuType) => cpuType switch
{
0x00000007 => "x86",
0x01000007 => "x86_64",
0x0000000C => "arm",
0x0100000C => "arm64",
_ => null,
};
private static void ParseElfNote(ReadOnlySpan<byte> note, bool bigEndian, ref string? buildId)
{
if (note.Length < 12)
{
return;
}
var namesz = bigEndian
? BinaryPrimitives.ReadUInt32BigEndian(note)
: BinaryPrimitives.ReadUInt32LittleEndian(note);
var descsz = bigEndian
? BinaryPrimitives.ReadUInt32BigEndian(note.Slice(4))
: BinaryPrimitives.ReadUInt32LittleEndian(note.Slice(4));
var type = bigEndian
? BinaryPrimitives.ReadUInt32BigEndian(note.Slice(8))
: BinaryPrimitives.ReadUInt32LittleEndian(note.Slice(8));
var offset = 12;
var nameEnd = offset + (int)namesz;
if (nameEnd > note.Length)
{
return;
}
var name = System.Text.Encoding.ASCII.GetString(note.Slice(offset, (int)namesz)).TrimEnd('\0');
offset = Align(nameEnd, 4);
var descEnd = offset + (int)descsz;
if (descEnd > note.Length)
{
return;
}
if (name == "GNU" && type == 3 && descsz > 0)
{
var desc = note.Slice(offset, (int)descsz);
buildId = Convert.ToHexString(desc).ToLowerInvariant();
}
}
private static int Align(int value, int alignment) => (value + (alignment - 1)) & ~(alignment - 1);
}
#pragma warning restore CA2022

View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>CA2022</NoWarn>
<WarningsNotAsErrors>CA2022</WarningsNotAsErrors>
</PropertyGroup>
</Project>