partly or unimplemented features - now implemented
This commit is contained in:
@@ -0,0 +1,337 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// CrossDistroCoverageService.cs
|
||||
// Sprint: SPRINT_20260208_027_BinaryIndex_cross_distro_golden_set_for_backport_validation
|
||||
// Task: T1 — Cross-distro coverage matrix service implementation
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.Metrics;
|
||||
|
||||
namespace StellaOps.BinaryIndex.GoldenSet;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory implementation of the cross-distro coverage matrix.
|
||||
/// Manages curated CVE entries with per-distro backport validation status.
|
||||
/// </summary>
|
||||
public sealed class CrossDistroCoverageService : ICrossDistroCoverageService
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, CuratedCveEntry> _entries = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
private readonly Counter<long> _upsertCounter;
|
||||
private readonly Counter<long> _queryCounter;
|
||||
private readonly Counter<long> _seedCounter;
|
||||
private readonly Counter<long> _validatedCounter;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new cross-distro coverage service with OTel instrumentation.
|
||||
/// </summary>
|
||||
public CrossDistroCoverageService(IMeterFactory meterFactory, TimeProvider? timeProvider = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(meterFactory);
|
||||
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
|
||||
var meter = meterFactory.Create("StellaOps.BinaryIndex.GoldenSet.CrossDistro");
|
||||
_upsertCounter = meter.CreateCounter<long>("crossdistro.upsert.total", description: "CVE entries upserted");
|
||||
_queryCounter = meter.CreateCounter<long>("crossdistro.query.total", description: "Coverage queries executed");
|
||||
_seedCounter = meter.CreateCounter<long>("crossdistro.seed.total", description: "Built-in entries seeded");
|
||||
_validatedCounter = meter.CreateCounter<long>("crossdistro.validated.total", description: "Entries marked as validated");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<CuratedCveEntry> UpsertAsync(CuratedCveEntry entry, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entry);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(entry.CveId);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var updated = entry with { UpdatedAt = now };
|
||||
_entries[entry.CveId] = updated;
|
||||
|
||||
_upsertCounter.Add(1);
|
||||
return Task.FromResult(updated);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<CuratedCveEntry?> GetByCveIdAsync(string cveId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(cveId);
|
||||
|
||||
_entries.TryGetValue(cveId, out var entry);
|
||||
return Task.FromResult(entry);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<ImmutableArray<CuratedCveEntry>> QueryAsync(CuratedCveQuery query, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(query);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
_queryCounter.Add(1);
|
||||
|
||||
IEnumerable<CuratedCveEntry> results = _entries.Values;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.Component))
|
||||
{
|
||||
results = results.Where(e =>
|
||||
e.Component.Contains(query.Component, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (query.Distro is { } distro)
|
||||
{
|
||||
results = results.Where(e =>
|
||||
!e.Coverage.IsDefaultOrEmpty &&
|
||||
e.Coverage.Any(c => c.Distro == distro));
|
||||
}
|
||||
|
||||
if (query.Status is { } status)
|
||||
{
|
||||
results = results.Where(e =>
|
||||
!e.Coverage.IsDefaultOrEmpty &&
|
||||
e.Coverage.Any(c => c.Status == status));
|
||||
}
|
||||
|
||||
if (query.OnlyUnvalidated)
|
||||
{
|
||||
results = results.Where(e =>
|
||||
!e.Coverage.IsDefaultOrEmpty &&
|
||||
e.Coverage.Any(c => !c.Validated));
|
||||
}
|
||||
|
||||
var ordered = results
|
||||
.OrderBy(e => e.CveId, StringComparer.OrdinalIgnoreCase)
|
||||
.Skip(query.Offset)
|
||||
.Take(query.Limit)
|
||||
.ToImmutableArray();
|
||||
|
||||
return Task.FromResult(ordered);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<CrossDistroCoverageSummary> GetSummaryAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var allEntries = _entries.Values.ToList();
|
||||
var allCoverage = allEntries
|
||||
.Where(e => !e.Coverage.IsDefaultOrEmpty)
|
||||
.SelectMany(e => e.Coverage)
|
||||
.ToList();
|
||||
|
||||
var byDistro = new Dictionary<DistroFamily, DistroBreakdown>();
|
||||
foreach (var distro in Enum.GetValues<DistroFamily>())
|
||||
{
|
||||
var distroEntries = allCoverage.Where(c => c.Distro == distro).ToList();
|
||||
byDistro[distro] = new DistroBreakdown
|
||||
{
|
||||
EntryCount = distroEntries.Count,
|
||||
ValidatedCount = distroEntries.Count(c => c.Validated),
|
||||
BackportedCount = distroEntries.Count(c => c.Status == BackportStatus.Backported)
|
||||
};
|
||||
}
|
||||
|
||||
var summary = new CrossDistroCoverageSummary
|
||||
{
|
||||
TotalCves = allEntries.Count,
|
||||
TotalEntries = allCoverage.Count,
|
||||
ValidatedEntries = allCoverage.Count(c => c.Validated),
|
||||
BackportedCount = allCoverage.Count(c => c.Status == BackportStatus.Backported),
|
||||
NotPatchedCount = allCoverage.Count(c => c.Status == BackportStatus.NotPatched),
|
||||
ByDistro = byDistro.ToImmutableDictionary()
|
||||
};
|
||||
|
||||
return Task.FromResult(summary);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<bool> SetValidatedAsync(
|
||||
string cveId,
|
||||
DistroFamily distro,
|
||||
string version,
|
||||
bool validated,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(cveId);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(version);
|
||||
|
||||
if (!_entries.TryGetValue(cveId, out var entry))
|
||||
return Task.FromResult(false);
|
||||
|
||||
if (entry.Coverage.IsDefaultOrEmpty)
|
||||
return Task.FromResult(false);
|
||||
|
||||
var index = -1;
|
||||
for (var i = 0; i < entry.Coverage.Length; i++)
|
||||
{
|
||||
var candidate = entry.Coverage[i];
|
||||
if (candidate.Distro == distro &&
|
||||
candidate.Version.Equals(version, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index < 0)
|
||||
return Task.FromResult(false);
|
||||
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var updated = entry.Coverage[index] with
|
||||
{
|
||||
Validated = validated,
|
||||
ValidatedAt = validated ? now : null
|
||||
};
|
||||
|
||||
var newCoverage = entry.Coverage.SetItem(index, updated);
|
||||
_entries[cveId] = entry with { Coverage = newCoverage, UpdatedAt = now };
|
||||
|
||||
_validatedCounter.Add(1);
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<int> SeedBuiltInEntriesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var seeded = 0;
|
||||
|
||||
foreach (var entry in CreateBuiltInEntries(now))
|
||||
{
|
||||
if (_entries.TryAdd(entry.CveId, entry))
|
||||
seeded++;
|
||||
}
|
||||
|
||||
_seedCounter.Add(seeded);
|
||||
return Task.FromResult(seeded);
|
||||
}
|
||||
|
||||
// ── Built-in curated CVEs for cross-distro backport validation ─────
|
||||
|
||||
internal static ImmutableArray<CuratedCveEntry> CreateBuiltInEntries(DateTimeOffset createdAt)
|
||||
{
|
||||
return
|
||||
[
|
||||
// OpenSSL Heartbleed — buffer over-read in TLS heartbeat extension
|
||||
new CuratedCveEntry
|
||||
{
|
||||
CveId = "CVE-2014-0160",
|
||||
Component = "openssl",
|
||||
CommonName = "Heartbleed",
|
||||
CvssScore = 7.5,
|
||||
CweIds = ["CWE-126"],
|
||||
GoldenSetId = "CVE-2014-0160",
|
||||
CreatedAt = createdAt,
|
||||
Coverage =
|
||||
[
|
||||
Entry(DistroFamily.Alpine, "3.9", "openssl", "1.0.2k-r0", BackportStatus.NotPatched),
|
||||
Entry(DistroFamily.Alpine, "3.18", "openssl", "3.1.1-r0", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Debian, "wheezy", "openssl", "1.0.1e-2+deb7u7", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Debian, "bookworm", "openssl", "3.0.11-1~deb12u1", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Rhel, "6", "openssl", "1.0.1e-16.el6_5.7", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Rhel, "9", "openssl", "3.0.7-17.el9", BackportStatus.Backported),
|
||||
]
|
||||
},
|
||||
|
||||
// sudo Baron Samedit — heap-based buffer overflow in sudoers parsing
|
||||
new CuratedCveEntry
|
||||
{
|
||||
CveId = "CVE-2021-3156",
|
||||
Component = "sudo",
|
||||
CommonName = "Baron Samedit",
|
||||
CvssScore = 7.8,
|
||||
CweIds = ["CWE-122"],
|
||||
GoldenSetId = "CVE-2021-3156",
|
||||
CreatedAt = createdAt,
|
||||
Coverage =
|
||||
[
|
||||
Entry(DistroFamily.Alpine, "3.12", "sudo", "1.9.5p2-r0", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Alpine, "3.18", "sudo", "1.9.13p3-r0", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Debian, "buster", "sudo", "1.8.27-1+deb10u3", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Debian, "bookworm", "sudo", "1.9.13p3-1+deb12u1", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Rhel, "7", "sudo", "1.8.23-10.el7_9.3", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Rhel, "9", "sudo", "1.9.5p2-9.el9", BackportStatus.Backported),
|
||||
]
|
||||
},
|
||||
|
||||
// glibc — stack buffer overflow in __nss_hostname_digits_dots
|
||||
new CuratedCveEntry
|
||||
{
|
||||
CveId = "CVE-2015-0235",
|
||||
Component = "glibc",
|
||||
CommonName = "GHOST",
|
||||
CvssScore = 10.0,
|
||||
CweIds = ["CWE-787"],
|
||||
GoldenSetId = "CVE-2015-0235",
|
||||
CreatedAt = createdAt,
|
||||
Coverage =
|
||||
[
|
||||
Entry(DistroFamily.Alpine, "3.18", "musl", "1.2.4-r0", BackportStatus.NotApplicable),
|
||||
Entry(DistroFamily.Debian, "wheezy", "eglibc", "2.13-38+deb7u8", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Debian, "bookworm", "glibc", "2.36-9+deb12u3", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Rhel, "6", "glibc", "2.12-1.149.el6_6.5", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Rhel, "9", "glibc", "2.34-60.el9", BackportStatus.Backported),
|
||||
]
|
||||
},
|
||||
|
||||
// curl — SOCKS5 heap-based buffer overflow
|
||||
new CuratedCveEntry
|
||||
{
|
||||
CveId = "CVE-2023-38545",
|
||||
Component = "curl",
|
||||
CommonName = "SOCKS5 heap overflow",
|
||||
CvssScore = 9.8,
|
||||
CweIds = ["CWE-787"],
|
||||
GoldenSetId = "CVE-2023-38545",
|
||||
CreatedAt = createdAt,
|
||||
Coverage =
|
||||
[
|
||||
Entry(DistroFamily.Alpine, "3.18", "curl", "8.4.0-r0", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Debian, "bookworm", "curl", "7.88.1-10+deb12u4", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Debian, "bullseye", "curl", "7.74.0-1.3+deb11u10", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Rhel, "8", "curl", "7.61.1-30.el8_8.4", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Rhel, "9", "curl", "8.0.1-1.el9", BackportStatus.Backported),
|
||||
]
|
||||
},
|
||||
|
||||
// OpenSSH — regreSSHion (signal handler race condition)
|
||||
new CuratedCveEntry
|
||||
{
|
||||
CveId = "CVE-2024-6387",
|
||||
Component = "openssh",
|
||||
CommonName = "regreSSHion",
|
||||
CvssScore = 8.1,
|
||||
CweIds = ["CWE-362"],
|
||||
GoldenSetId = "CVE-2024-6387",
|
||||
CreatedAt = createdAt,
|
||||
Coverage =
|
||||
[
|
||||
Entry(DistroFamily.Alpine, "3.18", "openssh", "9.3_p2-r0", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Alpine, "3.20", "openssh", "9.7_p1-r4", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Debian, "bookworm", "openssh", "1:9.2p1-2+deb12u3", BackportStatus.Backported),
|
||||
Entry(DistroFamily.Rhel, "8", "openssh", "8.0p1-19.el8_8", BackportStatus.NotPatched),
|
||||
Entry(DistroFamily.Rhel, "9", "openssh", "8.7p1-38.el9_4.1", BackportStatus.Backported),
|
||||
]
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private static DistroCoverageEntry Entry(
|
||||
DistroFamily distro,
|
||||
string version,
|
||||
string package,
|
||||
string packageVersion,
|
||||
BackportStatus status) => new()
|
||||
{
|
||||
Distro = distro,
|
||||
Version = version,
|
||||
PackageName = package,
|
||||
PackageVersion = packageVersion,
|
||||
Status = status
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// ICrossDistroCoverageService.cs
|
||||
// Sprint: SPRINT_20260208_027_BinaryIndex_cross_distro_golden_set_for_backport_validation
|
||||
// Task: T1 — Interface for cross-distro coverage matrix management
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StellaOps.BinaryIndex.GoldenSet;
|
||||
|
||||
/// <summary>
|
||||
/// Manages the cross-distro coverage matrix for curated CVEs,
|
||||
/// enabling backport validation across Alpine, Debian, and RHEL.
|
||||
/// </summary>
|
||||
public interface ICrossDistroCoverageService
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds or updates a curated CVE entry with its cross-distro coverage data.
|
||||
/// </summary>
|
||||
Task<CuratedCveEntry> UpsertAsync(CuratedCveEntry entry, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a curated CVE entry by its CVE ID.
|
||||
/// </summary>
|
||||
Task<CuratedCveEntry?> GetByCveIdAsync(string cveId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Queries curated CVE entries with filtering.
|
||||
/// </summary>
|
||||
Task<ImmutableArray<CuratedCveEntry>> QueryAsync(CuratedCveQuery query, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Computes a summary of cross-distro coverage across all curated CVEs.
|
||||
/// </summary>
|
||||
Task<CrossDistroCoverageSummary> GetSummaryAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Marks a specific distro coverage entry as validated (or not).
|
||||
/// </summary>
|
||||
Task<bool> SetValidatedAsync(
|
||||
string cveId,
|
||||
DistroFamily distro,
|
||||
string version,
|
||||
bool validated,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Seeds the coverage matrix with built-in high-impact CVE entries.
|
||||
/// Idempotent: only adds entries that don't already exist.
|
||||
/// </summary>
|
||||
Task<int> SeedBuiltInEntriesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
Reference in New Issue
Block a user