202 lines
7.1 KiB
C#
202 lines
7.1 KiB
C#
using System.Collections.Immutable;
|
|
using Microsoft.Extensions.Logging;
|
|
using StellaOps.BinaryIndex.Core.Models;
|
|
using StellaOps.BinaryIndex.Core.Services;
|
|
using StellaOps.BinaryIndex.FixIndex.Repositories;
|
|
using StellaOps.BinaryIndex.Fingerprints.Matching;
|
|
using StellaOps.BinaryIndex.Persistence.Repositories;
|
|
|
|
namespace StellaOps.BinaryIndex.Persistence.Services;
|
|
|
|
/// <summary>
|
|
/// Implementation of binary vulnerability lookup service.
|
|
/// </summary>
|
|
public sealed class BinaryVulnerabilityService : IBinaryVulnerabilityService
|
|
{
|
|
private readonly IBinaryVulnAssertionRepository _assertionRepo;
|
|
private readonly IFixIndexRepository? _fixIndexRepo;
|
|
private readonly IFingerprintMatcher? _fingerprintMatcher;
|
|
private readonly ILogger<BinaryVulnerabilityService> _logger;
|
|
|
|
public BinaryVulnerabilityService(
|
|
IBinaryVulnAssertionRepository assertionRepo,
|
|
ILogger<BinaryVulnerabilityService> logger,
|
|
IFixIndexRepository? fixIndexRepo = null,
|
|
IFingerprintMatcher? fingerprintMatcher = null)
|
|
{
|
|
_assertionRepo = assertionRepo;
|
|
_logger = logger;
|
|
_fixIndexRepo = fixIndexRepo;
|
|
_fingerprintMatcher = fingerprintMatcher;
|
|
}
|
|
|
|
public async Task<ImmutableArray<BinaryVulnMatch>> LookupByIdentityAsync(
|
|
BinaryIdentity identity,
|
|
LookupOptions? options = null,
|
|
CancellationToken ct = default)
|
|
{
|
|
options ??= new LookupOptions();
|
|
var matches = new List<BinaryVulnMatch>();
|
|
|
|
// Check explicit assertions
|
|
var assertions = await _assertionRepo.GetByBinaryKeyAsync(identity.BinaryKey, ct);
|
|
foreach (var assertion in assertions.Where(a => a.Status == "affected"))
|
|
{
|
|
matches.Add(new BinaryVulnMatch
|
|
{
|
|
CveId = assertion.CveId,
|
|
VulnerablePurl = "pkg:unknown", // Resolved from advisory
|
|
Method = MapMethod(assertion.Method),
|
|
Confidence = assertion.Confidence ?? 0.9m,
|
|
Evidence = new MatchEvidence { BuildId = identity.BuildId }
|
|
});
|
|
}
|
|
|
|
_logger.LogDebug("Found {Count} vulnerability matches for {BinaryKey}", matches.Count, identity.BinaryKey);
|
|
return matches.ToImmutableArray();
|
|
}
|
|
|
|
public async Task<ImmutableDictionary<string, ImmutableArray<BinaryVulnMatch>>> LookupBatchAsync(
|
|
IEnumerable<BinaryIdentity> identities,
|
|
LookupOptions? options = null,
|
|
CancellationToken ct = default)
|
|
{
|
|
var results = new Dictionary<string, ImmutableArray<BinaryVulnMatch>>();
|
|
|
|
foreach (var identity in identities)
|
|
{
|
|
var matches = await LookupByIdentityAsync(identity, options, ct);
|
|
results[identity.BinaryKey] = matches;
|
|
}
|
|
|
|
return results.ToImmutableDictionary();
|
|
}
|
|
|
|
public async Task<FixStatusResult?> GetFixStatusAsync(
|
|
string distro,
|
|
string release,
|
|
string sourcePkg,
|
|
string cveId,
|
|
CancellationToken ct = default)
|
|
{
|
|
if (_fixIndexRepo is null)
|
|
{
|
|
_logger.LogWarning("Fix index repository not configured, cannot check fix status");
|
|
return null;
|
|
}
|
|
|
|
var entry = await _fixIndexRepo.GetFixStatusAsync(distro, release, sourcePkg, cveId, ct);
|
|
if (entry is null)
|
|
{
|
|
_logger.LogDebug("No fix status found for {CveId} in {Distro}/{Release}/{Package}",
|
|
cveId, distro, release, sourcePkg);
|
|
return null;
|
|
}
|
|
|
|
return new FixStatusResult
|
|
{
|
|
State = entry.State,
|
|
FixedVersion = entry.FixedVersion,
|
|
Method = entry.Method,
|
|
Confidence = entry.Confidence,
|
|
EvidenceId = entry.EvidenceId
|
|
};
|
|
}
|
|
|
|
public async Task<ImmutableDictionary<string, FixStatusResult>> GetFixStatusBatchAsync(
|
|
string distro,
|
|
string release,
|
|
string sourcePkg,
|
|
IEnumerable<string> cveIds,
|
|
CancellationToken ct = default)
|
|
{
|
|
var results = new Dictionary<string, FixStatusResult>();
|
|
|
|
if (_fixIndexRepo is null)
|
|
{
|
|
_logger.LogWarning("Fix index repository not configured, cannot check fix status");
|
|
return results.ToImmutableDictionary();
|
|
}
|
|
|
|
foreach (var cveId in cveIds)
|
|
{
|
|
var status = await GetFixStatusAsync(distro, release, sourcePkg, cveId, ct);
|
|
if (status is not null)
|
|
{
|
|
results[cveId] = status;
|
|
}
|
|
}
|
|
|
|
_logger.LogDebug("Found fix status for {Count} CVEs in {Distro}/{Release}/{Package}",
|
|
results.Count, distro, release, sourcePkg);
|
|
return results.ToImmutableDictionary();
|
|
}
|
|
|
|
private static MatchMethod MapMethod(string method) => method switch
|
|
{
|
|
"buildid_catalog" => MatchMethod.BuildIdCatalog,
|
|
"fingerprint_match" => MatchMethod.FingerprintMatch,
|
|
_ => MatchMethod.RangeMatch
|
|
};
|
|
|
|
public async Task<ImmutableArray<BinaryVulnMatch>> LookupByFingerprintAsync(
|
|
byte[] fingerprint,
|
|
FingerprintLookupOptions? options = null,
|
|
CancellationToken ct = default)
|
|
{
|
|
if (_fingerprintMatcher is null)
|
|
{
|
|
_logger.LogWarning("Fingerprint matcher not configured, cannot perform fingerprint lookup");
|
|
return ImmutableArray<BinaryVulnMatch>.Empty;
|
|
}
|
|
|
|
options ??= new FingerprintLookupOptions();
|
|
var matches = new List<BinaryVulnMatch>();
|
|
|
|
var matchOptions = new MatchOptions
|
|
{
|
|
MinSimilarity = options.MinSimilarity,
|
|
MaxCandidates = options.MaxCandidates,
|
|
Architecture = options.Architecture
|
|
};
|
|
|
|
var result = await _fingerprintMatcher.MatchAsync(fingerprint, matchOptions, ct).ConfigureAwait(false);
|
|
|
|
if (result.IsMatch && result.MatchedFingerprint is not null)
|
|
{
|
|
var fp = result.MatchedFingerprint;
|
|
matches.Add(new BinaryVulnMatch
|
|
{
|
|
CveId = fp.CveId,
|
|
VulnerablePurl = fp.Purl ?? $"pkg:generic/{fp.Component}",
|
|
Method = MatchMethod.FingerprintMatch,
|
|
Confidence = result.Confidence,
|
|
Evidence = new MatchEvidence
|
|
{
|
|
Similarity = result.Similarity,
|
|
MatchedFunction = fp.FunctionName
|
|
}
|
|
});
|
|
}
|
|
|
|
_logger.LogDebug("Fingerprint lookup found {Count} matches", matches.Count);
|
|
return matches.ToImmutableArray();
|
|
}
|
|
|
|
public async Task<ImmutableDictionary<string, ImmutableArray<BinaryVulnMatch>>> LookupByFingerprintBatchAsync(
|
|
IEnumerable<(string Key, byte[] Fingerprint)> fingerprints,
|
|
FingerprintLookupOptions? options = null,
|
|
CancellationToken ct = default)
|
|
{
|
|
var results = new Dictionary<string, ImmutableArray<BinaryVulnMatch>>();
|
|
|
|
foreach (var (key, fingerprint) in fingerprints)
|
|
{
|
|
var matches = await LookupByFingerprintAsync(fingerprint, options, ct).ConfigureAwait(false);
|
|
results[key] = matches;
|
|
}
|
|
|
|
return results.ToImmutableDictionary();
|
|
}
|
|
}
|