305 lines
10 KiB
C#
305 lines
10 KiB
C#
using System.Text.Json.Serialization;
|
|
|
|
namespace StellaOps.BinaryIndex.Builders;
|
|
|
|
/// <summary>
|
|
/// A claim asserting a CVE verdict for a specific fingerprint.
|
|
/// Created when reproducible builds show a function was modified to fix a CVE.
|
|
/// </summary>
|
|
public sealed record FingerprintClaim
|
|
{
|
|
/// <summary>
|
|
/// Unique identifier for this claim.
|
|
/// </summary>
|
|
public Guid Id { get; init; }
|
|
|
|
/// <summary>
|
|
/// ID of the fingerprint this claim is about.
|
|
/// </summary>
|
|
public required Guid FingerprintId { get; init; }
|
|
|
|
/// <summary>
|
|
/// CVE identifier (e.g., "CVE-2023-12345").
|
|
/// </summary>
|
|
public required string CveId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Verdict: whether this fingerprint is fixed, vulnerable, or unknown.
|
|
/// </summary>
|
|
public required ClaimVerdict Verdict { get; init; }
|
|
|
|
/// <summary>
|
|
/// Evidence supporting this claim.
|
|
/// </summary>
|
|
public required FingerprintClaimEvidence Evidence { get; init; }
|
|
|
|
/// <summary>
|
|
/// Hash of the DSSE attestation if signed.
|
|
/// </summary>
|
|
public string? AttestationDsseHash { get; init; }
|
|
|
|
/// <summary>
|
|
/// When this claim was created.
|
|
/// </summary>
|
|
public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.UtcNow;
|
|
|
|
/// <summary>
|
|
/// When this claim was last updated.
|
|
/// </summary>
|
|
public DateTimeOffset? UpdatedAt { get; init; }
|
|
|
|
/// <summary>
|
|
/// Source that generated this claim (e.g., "repro-builder-alpine").
|
|
/// </summary>
|
|
public string? Source { get; init; }
|
|
|
|
/// <summary>
|
|
/// Confidence in this claim (0.0-1.0).
|
|
/// </summary>
|
|
public decimal Confidence { get; init; } = 1.0m;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verdict for a fingerprint claim.
|
|
/// </summary>
|
|
[JsonConverter(typeof(JsonStringEnumConverter))]
|
|
public enum ClaimVerdict
|
|
{
|
|
/// <summary>
|
|
/// The fingerprint is from a binary that contains the CVE fix.
|
|
/// </summary>
|
|
Fixed,
|
|
|
|
/// <summary>
|
|
/// The fingerprint is from a binary that is vulnerable to the CVE.
|
|
/// </summary>
|
|
Vulnerable,
|
|
|
|
/// <summary>
|
|
/// Unable to determine fix status.
|
|
/// </summary>
|
|
Unknown
|
|
}
|
|
|
|
/// <summary>
|
|
/// Evidence supporting a fingerprint claim.
|
|
/// </summary>
|
|
public sealed record FingerprintClaimEvidence
|
|
{
|
|
/// <summary>
|
|
/// Git commit or patch reference that introduced the fix.
|
|
/// </summary>
|
|
public required string PatchCommit { get; init; }
|
|
|
|
/// <summary>
|
|
/// List of function names that changed between vulnerable and fixed versions.
|
|
/// </summary>
|
|
public required IReadOnlyList<string> ChangedFunctions { get; init; }
|
|
|
|
/// <summary>
|
|
/// Similarity scores for modified functions (function name -> score).
|
|
/// </summary>
|
|
public IReadOnlyDictionary<string, decimal>? FunctionSimilarities { get; init; }
|
|
|
|
/// <summary>
|
|
/// Reference to the vulnerable build artifacts.
|
|
/// </summary>
|
|
public string? VulnerableBuildRef { get; init; }
|
|
|
|
/// <summary>
|
|
/// Reference to the patched build artifacts.
|
|
/// </summary>
|
|
public string? PatchedBuildRef { get; init; }
|
|
|
|
/// <summary>
|
|
/// Source package name.
|
|
/// </summary>
|
|
public string? SourcePackage { get; init; }
|
|
|
|
/// <summary>
|
|
/// Vulnerable version string.
|
|
/// </summary>
|
|
public string? VulnerableVersion { get; init; }
|
|
|
|
/// <summary>
|
|
/// Patched version string.
|
|
/// </summary>
|
|
public string? PatchedVersion { get; init; }
|
|
|
|
/// <summary>
|
|
/// Distro and release this build was done for.
|
|
/// </summary>
|
|
public string? DistroRelease { get; init; }
|
|
|
|
/// <summary>
|
|
/// Builder image used for reproducible builds.
|
|
/// </summary>
|
|
public string? BuilderImage { get; init; }
|
|
|
|
/// <summary>
|
|
/// Timestamp of the vulnerable build.
|
|
/// </summary>
|
|
public DateTimeOffset? VulnerableBuildTimestamp { get; init; }
|
|
|
|
/// <summary>
|
|
/// Timestamp of the patched build.
|
|
/// </summary>
|
|
public DateTimeOffset? PatchedBuildTimestamp { get; init; }
|
|
|
|
/// <summary>
|
|
/// Diff statistics summary.
|
|
/// </summary>
|
|
public DiffStatistics? DiffStatistics { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Repository for managing fingerprint claims.
|
|
/// </summary>
|
|
public interface IFingerprintClaimRepository
|
|
{
|
|
/// <summary>
|
|
/// Creates a new fingerprint claim.
|
|
/// </summary>
|
|
/// <param name="claim">The claim to create.</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
/// <returns>The created claim ID.</returns>
|
|
Task<Guid> CreateClaimAsync(FingerprintClaim claim, CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Creates multiple claims in a batch.
|
|
/// </summary>
|
|
/// <param name="claims">Claims to create.</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
Task CreateClaimsBatchAsync(IEnumerable<FingerprintClaim> claims, CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Gets a claim by ID.
|
|
/// </summary>
|
|
/// <param name="id">Claim ID.</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
/// <returns>The claim if found.</returns>
|
|
Task<FingerprintClaim?> GetClaimByIdAsync(Guid id, CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Gets all claims for a specific fingerprint.
|
|
/// </summary>
|
|
/// <param name="fingerprintId">Fingerprint ID.</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
/// <returns>List of claims for the fingerprint.</returns>
|
|
Task<IReadOnlyList<FingerprintClaim>> GetClaimsByFingerprintAsync(
|
|
Guid fingerprintId,
|
|
CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Gets all claims for a specific fingerprint hash.
|
|
/// </summary>
|
|
/// <param name="fingerprintHash">Fingerprint hash (hex-encoded).</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
/// <returns>List of claims for the fingerprint.</returns>
|
|
Task<IReadOnlyList<FingerprintClaim>> GetClaimsByFingerprintHashAsync(
|
|
string fingerprintHash,
|
|
CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Gets all claims for a specific CVE.
|
|
/// </summary>
|
|
/// <param name="cveId">CVE identifier.</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
/// <returns>List of claims for the CVE.</returns>
|
|
Task<IReadOnlyList<FingerprintClaim>> GetClaimsByCveAsync(
|
|
string cveId,
|
|
CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Gets claims with a specific verdict.
|
|
/// </summary>
|
|
/// <param name="verdict">Verdict to filter by.</param>
|
|
/// <param name="limit">Maximum results to return.</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
/// <returns>List of claims with the verdict.</returns>
|
|
Task<IReadOnlyList<FingerprintClaim>> GetClaimsByVerdictAsync(
|
|
ClaimVerdict verdict,
|
|
int limit = 100,
|
|
CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Updates an existing claim.
|
|
/// </summary>
|
|
/// <param name="claim">The updated claim.</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
Task UpdateClaimAsync(FingerprintClaim claim, CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Deletes a claim by ID.
|
|
/// </summary>
|
|
/// <param name="id">Claim ID.</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
/// <returns>True if deleted, false if not found.</returns>
|
|
Task<bool> DeleteClaimAsync(Guid id, CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Checks if a claim already exists for a fingerprint+CVE combination.
|
|
/// </summary>
|
|
/// <param name="fingerprintId">Fingerprint ID.</param>
|
|
/// <param name="cveId">CVE identifier.</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
/// <returns>True if a claim exists.</returns>
|
|
Task<bool> ClaimExistsAsync(Guid fingerprintId, string cveId, CancellationToken ct = default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Repository for managing function fingerprints (per-binary breakdown).
|
|
/// </summary>
|
|
public interface IFunctionFingerprintRepository
|
|
{
|
|
/// <summary>
|
|
/// Stores function fingerprints for a binary.
|
|
/// </summary>
|
|
/// <param name="binaryFingerprintId">Parent binary fingerprint ID.</param>
|
|
/// <param name="functions">Function fingerprints to store.</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
Task StoreFunctionsAsync(
|
|
Guid binaryFingerprintId,
|
|
IEnumerable<FunctionFingerprint> functions,
|
|
CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Gets all function fingerprints for a binary.
|
|
/// </summary>
|
|
/// <param name="binaryFingerprintId">Parent binary fingerprint ID.</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
/// <returns>List of function fingerprints.</returns>
|
|
Task<IReadOnlyList<FunctionFingerprint>> GetFunctionsByBinaryAsync(
|
|
Guid binaryFingerprintId,
|
|
CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Searches for functions by name pattern.
|
|
/// </summary>
|
|
/// <param name="namePattern">Function name pattern (SQL LIKE).</param>
|
|
/// <param name="limit">Maximum results.</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
/// <returns>Matching functions with their binary IDs.</returns>
|
|
Task<IReadOnlyList<(Guid BinaryId, FunctionFingerprint Function)>> SearchFunctionsByNameAsync(
|
|
string namePattern,
|
|
int limit = 100,
|
|
CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Finds functions matching a specific basic block hash.
|
|
/// </summary>
|
|
/// <param name="basicBlockHash">Hash to search for.</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
/// <returns>Matching functions with their binary IDs.</returns>
|
|
Task<IReadOnlyList<(Guid BinaryId, FunctionFingerprint Function)>> FindByBasicBlockHashAsync(
|
|
byte[] basicBlockHash,
|
|
CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Deletes all function fingerprints for a binary.
|
|
/// </summary>
|
|
/// <param name="binaryFingerprintId">Parent binary fingerprint ID.</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
Task DeleteFunctionsByBinaryAsync(Guid binaryFingerprintId, CancellationToken ct = default);
|
|
}
|