From f2565a32246af5e6c34879b9edb68d9391418e1b Mon Sep 17 00:00:00 2001 From: master <> Date: Tue, 30 Dec 2025 11:26:17 +0200 Subject: [PATCH] add sprint for improved backported CVE patches --- ...0251230_001_BE_backport_resolver_DESIGN.md | 678 +++++++++++ ...20251230_001_BE_backport_resolver_TESTS.md | 1039 +++++++++++++++++ ...01_BE_backport_resolver_tiered_evidence.md | 906 ++++++++++++++ 3 files changed, 2623 insertions(+) create mode 100644 docs/implplan/SPRINT_20251230_001_BE_backport_resolver_DESIGN.md create mode 100644 docs/implplan/SPRINT_20251230_001_BE_backport_resolver_TESTS.md create mode 100644 docs/implplan/SPRINT_20251230_001_BE_backport_resolver_tiered_evidence.md diff --git a/docs/implplan/SPRINT_20251230_001_BE_backport_resolver_DESIGN.md b/docs/implplan/SPRINT_20251230_001_BE_backport_resolver_DESIGN.md new file mode 100644 index 000000000..2e3accc5c --- /dev/null +++ b/docs/implplan/SPRINT_20251230_001_BE_backport_resolver_DESIGN.md @@ -0,0 +1,678 @@ +# Backport Resolver Tiered Evidence - Implementation Design +**Sprint:** SPRINT_20251230_001_BE +**Version:** 1.0 +**Last Updated:** 2025-12-30 + +--- + +## Table of Contents +1. [Architecture Overview](#architecture-overview) +2. [Component Design](#component-design) +3. [Data Models](#data-models) +4. [Algorithms](#algorithms) +5. [Integration Points](#integration-points) +6. [Security & Compliance](#security--compliance) + +--- + +## 1. Architecture Overview + +### 1.1 Current State + +``` + + BackportStatusService + + EvalPatchedStatusAsync() + GetRulesAsync() from repository + EvaluateBoundaryRules() [STRING COMPARE ] + EvaluateRangeRules() [RETURNS UNKNOWN ] + Return verdict + + + + Consumes rules from + + + IFixRuleRepository + (OVAL/CSAF/Changelog rules) + - Only native distro + - No derivative mapping + +``` + +**Problems:** +- String comparison fails for version semantics (epoch, tildes, etc.) +- RangeRule logic not implemented always returns Unknown +- No cross-distro evidence reuse (AlmaLinux OVAL for RHEL) +- No bug ID CVE resolution + +### 1.2 Target State + +``` + + BackportStatusService + + EvalPatchedStatusAsync() + **Tier 1**: FetchRulesWithDerivativeMapping() [NEW] + Query RHEL try Alma/Rocky if not found + **Tier 2-4**: GetRulesAsync() (existing) + **Tier 5**: EvaluateRangeRules() [FIXED] + Hierarchical resolver with version comparators [NEW] + + + + IReadOnlyDictionary + RPM RpmVersionComparer (epoch:version-release) + Deb DebianVersionComparer (epoch:upstream-debian~pre) + Alpine ApkVersionComparer (X.Y.Z_pN-rN) + Fallback StringVersionComparer + + + + + Rules BugCVE mapping + + + IFixRuleRepository IBugCveMappingService + + DistroMappings DebianSecurityTracker + + ChangelogParser RedHatBugzilla (stub) + (with Bug IDs) UbuntuCVETracker + +``` + +--- + +## 2. Component Design + +### 2.1 BackportStatusService (Enhanced) + +**Responsibilities:** +- Orchestrate 5-tier evidence hierarchy +- Inject and delegate to version comparators +- Apply derivative distro mapping logic +- Aggregate evidence from multiple tiers +- Return confident verdicts with audit trails + +**Key Methods:** + +```csharp +public sealed class BackportStatusService +{ + private readonly IFixRuleRepository _ruleRepository; + private readonly IReadOnlyDictionary _comparators; + private readonly IBugCveMappingService? _bugMapper; // Optional + + // TIER 1: Try derivative OVAL/CSAF + private async ValueTask> FetchRulesWithDerivativeMapping( + BackportContext context, + PackageInstance package, + CveId cve, + CancellationToken ct); + + // TIER 2-4: Existing rule sources (unchanged) + // TIER 5: Evaluate NVD ranges with version comparators + private BackportVerdict EvaluateRangeRules( + CveId cve, + PackageInstance package, + IReadOnlyList rules); + + // Helper: Get comparator for ecosystem + private IVersionComparator GetComparatorForEcosystem(PackageEcosystem ecosystem) => + _comparators.GetValueOrDefault(ecosystem, StringVersionComparer.Instance); +} +``` + +**Dependency Injection:** + +```csharp +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +``` + +--- + +### 2.2 DistroMappings (New Component) + +**File:** src/__Libraries/StellaOps.DistroIntel/DistroDerivative.cs + +**Purpose:** Define and query derivative distro relationships (RHELAlma/Rocky, etc.) + +**Data Model:** + +```csharp +public enum DerivativeConfidence +{ + High, // ABI-compatible rebuilds (Alma/Rocky RHEL) + Medium // Modified derivatives (Mint Ubuntu, Ubuntu Debian) +} + +public sealed record DistroDerivative( + string CanonicalDistro, // "rhel" + string DerivativeDistro, // "almalinux" + int MajorRelease, // 9 + DerivativeConfidence Confidence); + +public static class DistroMappings +{ + public static readonly ImmutableArray Derivatives = [...]; + + public static IEnumerable FindDerivativesFor( + string distro, + int majorRelease); + + public static decimal GetConfidenceMultiplier(DerivativeConfidence conf); +} +``` + +**Usage Pattern:** + +```csharp +// When fetching rules for Rocky 9: +var derivatives = DistroMappings.FindDerivativesFor("rhel", 9); +// Returns: [("rhel", "almalinux", 9, High), ("rhel", "rocky", 9, High)] + +foreach (var d in derivatives.OrderByDescending(x => x.Confidence)) +{ + var derivativeRules = await _repo.GetRulesAsync(ctx with { Distro = d.DerivativeDistro }, ...); + if (derivativeRules.Any()) + { + // Apply 0.95 multiplier for High confidence + return derivativeRules.Select(r => r with { + Confidence = r.Confidence * 0.95m + }); + } +} +``` + +--- + +### 2.3 IBugCveMappingService (New Interface) + +**File:** src/__Libraries/StellaOps.BugTracking/IBugCveMappingService.cs + +**Purpose:** Resolve distro bug IDs to CVE IDs + +**Interface:** + +```csharp +public interface IBugCveMappingService +{ + ValueTask> LookupCvesAsync( + BugId bugId, + CancellationToken ct = default); +} + +public sealed record BugId(string Tracker, string Id); +``` + +**Implementations:** + +1. **DebianSecurityTrackerClient** + - Source: https://security-tracker.debian.org/tracker/data/json + - Caching: 1h TTL, in-memory + +2. **RedHatBugzillaClient** (stub) + - Requires authentication cache pre-populated mappings + - Future: integrate with RHBZ API + +3. **UbuntuCVETrackerClient** + - Source: https://ubuntu.com/security/cves scraper + - Caching: 1h TTL + +4. **CompositeBugCveMappingService** + - Routes to correct implementation based on BugId.Tracker + +**Example:** + +```csharp +var bugId = new BugId("Debian", "987654"); +var cves = await _bugMapper.LookupCvesAsync(bugId); +// Returns: [CVE-2024-1234, CVE-2024-5678] +``` + +--- + +### 2.4 ChangelogParser (Enhanced) + +**File:** src/Concelier/__Libraries/StellaOps.Concelier.SourceIntel/ChangelogParser.cs + +**Changes:** +- Add regex patterns for bug IDs (Debian, RHBZ, Launchpad) +- Extend ChangelogEntry record to include BugIds collection +- Extract both CVE IDs and bug IDs in parallel + +**Updated Model:** + +```csharp +public sealed record ChangelogEntry( + string Version, + DateTimeOffset Date, + IReadOnlyList CveIds, + IReadOnlyList BugIds, // NEW + string Description); +``` + +**Regex Patterns:** + +```csharp +[GeneratedRegex(@"CVE-\d{4}-\d{4,}")] +private static partial Regex CvePatternRegex(); // Existing + +[GeneratedRegex(@"Closes:\s*#(\d+)", RegexOptions.IgnoreCase)] +private static partial Regex DebianBugRegex(); // NEW + +[GeneratedRegex(@"(?:RHBZ|rhbz)#(\d+)", RegexOptions.IgnoreCase)] +private static partial Regex RhBugzillaRegex(); // NEW + +[GeneratedRegex(@"LP:\s*#(\d+)", RegexOptions.IgnoreCase)] +private static partial Regex LaunchpadBugRegex(); // NEW +``` + +--- + +### 2.5 HunkSigExtractor (Enhanced) + +**File:** src/Feedser/StellaOps.Feedser.Core/HunkSigExtractor.cs + +**Changes:** +- Extract function signatures from patch context +- Populate PatchHunkSig.AffectedFunctions (currently null) +- Support C/C++, Python, Go function patterns + +**Function Extraction Logic:** + +```csharp +private static IReadOnlyList ExtractFunctionsFromContext(PatchHunk hunk) +{ + var functions = new HashSet(); + + // C/C++: "static void foo(" or "int bar(" + foreach (Match m in CFunctionRegex().Matches(hunk.Context)) + functions.Add(m.Groups[1].Value); + + // Python: "def foo(" or "class Bar:" + foreach (Match m in PythonFunctionRegex().Matches(hunk.Context)) + functions.Add(m.Groups[1].Value); + + // Go: "func (r *Receiver) Method(" + foreach (Match m in GoFunctionRegex().Matches(hunk.Context)) + functions.Add(m.Groups[1].Value); + + return functions.ToArray(); +} + +// Usage: +AffectedFunctions = ExtractFunctionsFromContext(hunk), +``` + +--- + +## 3. Data Models + +### 3.1 BackportVerdict (Existing, No Changes) + +```csharp +public sealed record BackportVerdict( + FixStatus Status, // Fixed | Vulnerable | Unknown + VerdictConfidence Confidence, // High | Medium | Low + RuleType EvidenceSource, // Boundary | Range | Changelog | Patch + EvidencePointer EvidencePointer, // URI, digest, timestamp + string? ConflictReason); +``` + +### 3.2 RulePriority (Updated Enum) + +```csharp +public enum RulePriority +{ + // Tier 1: OVAL/CSAF evidence + DistroNativeOval = 100, // Distro's own OVAL/CSAF + DerivativeOvalHigh = 95, // Alma/Rocky for RHEL + DerivativeOvalMedium = 90, // Mint for Ubuntu + + // Tier 2: Changelog evidence + ChangelogExplicitCve = 85, // Direct CVE mention + ChangelogBugIdMapped = 75, // Bug ID CVE mapping + + // Tier 3: Source patches + SourcePatchExactMatch = 70, // Exact hunk hash match + SourcePatchFuzzyMatch = 60, // Function name + context match + + // Tier 4: Upstream commits + UpstreamCommitExactParity = 55, // 100% hunk parity + UpstreamCommitPartialMatch = 45, // Partial context match + + // Tier 5: NVD range heuristic + NvdRangeHeuristic = 20 // Version range check (low confidence) +} +``` + +### 3.3 EvidencePointer (Existing, Extended) + +```csharp +public sealed record EvidencePointer( + string Type, // "OvalAdvisory" | "DebianChangelog" | "NvdCpeRange" + string Uri, // "oval:ALSA-2024-1234" | "deb:curl/changelog#L42" + string SourceDigest, // SHA-256 of artifact + DateTimeOffset FetchedAt); +``` + +**New URI Schemes:** +- derivative:almalinuxrhel:oval:ALSA-2024-1234 (Tier 1) +- changelog:debian:curl:1.2.3#bug:987654 (Tier 2 with bug ID) +- +vd:cve/CVE-2024-1234/cpe:2.3:a:vendor:product:* (Tier 5) + +--- + +## 4. Algorithms + +### 4.1 Hierarchical Evidence Resolver + +```pseudo +FUNCTION ResolveFixStatus(cve, package, distro, release): + + // TIER 1: Try derivative OVAL/CSAF + rules FetchNativeRules(distro, release, package, cve) + + IF rules.IsEmpty THEN + derivatives DistroMappings.FindDerivativesFor(distro, release) + FOR EACH derivative IN derivatives ORDER BY Confidence DESC: + derivativeRules FetchNativeRules( + derivative.DerivativeDistro, + release, + package, + cve) + + IF derivativeRules.IsNotEmpty THEN + confidenceMultiplier derivative.Confidence == High ? 0.95 : 0.80 + rules ApplyConfidencePenalty(derivativeRules, confidenceMultiplier) + BREAK // Use first successful derivative + END IF + END FOR + END IF + + // TIER 2-4: Existing sources (changelog, patches, commits) + IF rules.IsEmpty THEN + rules FetchEvidenceBasedRules(distro, package, cve) + END IF + + // TIER 5: NVD range fallback + IF rules.IsEmpty THEN + rules FetchNvdRangeRules(cve, package) + END IF + + // Evaluate rules with version comparators + RETURN EvaluateRulesWithVersionSemantics(rules, package) +END FUNCTION +``` + +### 4.2 Version Comparison with Ecosystem-Specific Logic + +```pseudo +FUNCTION CompareVersions(v1, v2, ecosystem): + comparator GetComparatorForEcosystem(ecosystem) + + MATCH ecosystem: + CASE RPM: + // Parse epoch:version-release + // Compare epoch first, then version, then release + // Handle ~ (pre-release) and ^ (post-release) + RETURN RpmVersionComparer.CompareWithProof(v1, v2) + + CASE Debian: + // Parse epoch:upstream-debian~pre + // Tilde sorting: 1.0~beta < 1.0 + RETURN DebianVersionComparer.CompareWithProof(v1, v2) + + CASE Alpine: + // Parse X.Y.Z_pN-rN + // _p = patch level, -r = package revision + RETURN ApkVersionComparer.CompareWithProof(v1, v2) + + DEFAULT: + // Fallback to SemVer or string comparison + RETURN StringVersionComparer.Compare(v1, v2) + END MATCH +END FUNCTION +``` + +### 4.3 Range Evaluation (Tier 5) + +```pseudo +FUNCTION EvaluateRangeRules(cve, package, rangeRules): + comparator GetComparatorForEcosystem(package.Ecosystem) + + FOR EACH rule IN rangeRules ORDER BY Priority DESC: + range rule.AffectedRange + inRange TRUE + + // Check lower bound + IF range.MinVersion IS NOT NULL THEN + cmp comparator.Compare(package.Version, range.MinVersion) + inRange inRange AND (range.MinInclusive ? cmp >= 0 : cmp > 0) + END IF + + // Check upper bound + IF range.MaxVersion IS NOT NULL THEN + cmp comparator.Compare(package.Version, range.MaxVersion) + inRange inRange AND (range.MaxInclusive ? cmp <= 0 : cmp < 0) + END IF + + IF inRange THEN + RETURN Verdict(Status: VULNERABLE, Confidence: LOW, Evidence: rule) + END IF + END FOR + + RETURN Verdict(Status: UNKNOWN, Confidence: LOW) +END FUNCTION +``` + +### 4.4 Confidence Scoring + +```pseudo +FUNCTION GetConfidenceForPriority(priority): + IF priority >= 75 THEN // Tier 1-2 + RETURN VerdictConfidence.High + ELSE IF priority >= 45 THEN // Tier 3-4 + RETURN VerdictConfidence.Medium + ELSE // Tier 5 + RETURN VerdictConfidence.Low + END IF +END FUNCTION +``` + +--- + +## 5. Integration Points + +### 5.1 Feedser Integration (Evidence Ingestion) + +**Components:** +- OvalFeedProcessor Tier 1 (OVAL advisory parsing) +- CsafFeedProcessor Tier 1 (CSAF VEX parsing) +- ChangelogFeedProcessor Tier 2 (enhanced with bug ID extraction) +- PatchFeedProcessor Tier 3 (HunkSigExtractor with functions) + +**Data Flow:** + +``` +Feedser Ingestion Pipeline + + OVAL/CSAF Normalize Store with distro tags + (almalinux, rocky, rhel) + + Changelogs Parse Extract CVEs + Bug IDs + Map Bug IDs to CVEs (async) + + Patches Extract hunks Compute hunk sigs + functions + Store in content-addressed storage +``` + +### 5.2 VexLens Integration (Verdict Consumption) + +**Components:** +- VexConsensusEngine Aggregates verdicts from BackportStatusService +- CycloneDxVexEmitter Emits signed VEX statements with evidence + +**Enhancements:** +- Include EvidencePointer URIs in VEX statements +- Add confidence field (mapped from VerdictConfidence) +- Annotate Tier 5 verdicts with justification: "range-based heuristic" + +**Example VEX Output:** + +```json +{ + "vulnerability": { + "id": "CVE-2024-1234" + }, + "analysis": { + "state": "resolved", + "justification": "code_not_present", + "responses": ["will_not_fix", "update"], + "detail": "Fixed in curl-7.76.1-26.el9_3.2 (backport)", + "confidence": "high", + "evidence": [ + { + "type": "OvalAdvisory", + "uri": "derivative:almalinuxrocky:oval:ALSA-2024-1234", + "digest": "sha256:abc123...", + "tier": 1 + } + ] + } +} +``` + +### 5.3 External API Integrations + +| API | Purpose | Caching | Fallback | +|-----|---------|---------|----------| +| Debian Security Tracker | Bug ID CVE mapping | 1h TTL | Skip bug ID evidence | +| Red Hat Bugzilla | Bug ID CVE mapping | Pre-populated cache | Skip bug ID evidence | +| Ubuntu CVE Tracker | Bug ID CVE mapping | 1h TTL | Skip bug ID evidence | + +**Rate Limiting:** +- Debian: No explicit limit, but batch requests every 5 minutes +- RHBZ: Requires auth, use cached dump +- Ubuntu: Scraper-based, respect robots.txt (1 req/sec) + +--- + +## 6. Security & Compliance + +### 6.1 Evidence Integrity + +**Requirements:** +- All evidence artifacts must be cryptographically hashed (SHA-256) +- Store SourceDigest in EvidencePointer +- Enable deterministic replay by re-fetching and re-hashing + +**Implementation:** + +```csharp +public static string ComputeDigest(byte[] artifact) => + Convert.ToHexString(SHA256.HashData(artifact)).ToLowerInvariant(); + +var digest = ComputeDigest(Encoding.UTF8.GetBytes(ovalXml)); +var pointer = new EvidencePointer( + Type: "OvalAdvisory", + Uri: $"oval:ALSA-2024-1234", + SourceDigest: digest, + FetchedAt: DateTimeOffset.UtcNow); +``` + +### 6.2 Audit Trail + +**Logging Requirements:** +- Log every tier attempted (1 2 ... 5) +- Log reason for tier fallback (e.g., "Tier 1: no OVAL found for rocky 9") +- Log derivative mapping decisions (e.g., "Using AlmaLinux OVAL for Rocky 9, confidence penalty 0.05") +- Log version comparison details (e.g., "1:2.0 > 3.0 (epoch wins)") + +**Structured Logging Format:** + +```json +{ + "timestamp": "2025-12-30T12:34:56Z", + "level": "INFO", + "message": "Tier 1 fallback: derivative OVAL found", + "cve": "CVE-2024-1234", + "package": "curl-7.76.1-26.el9_3.2", + "distro": "rocky 9", + "derivativeUsed": "almalinux 9", + "confidence": 0.95, + "tier": 1, + "evidenceUri": "derivative:almalinuxrocky:oval:ALSA-2024-1234" +} +``` + +### 6.3 Signed VEX Attestations + +**Signature Method:** in-toto/DSSE with Ed25519 keys + +**Signed Payload:** + +```json +{ + "payloadType": "application/vnd.cyclonedx+json", + "payload": "", + "signatures": [ + { + "keyid": "SHA256:abc123...", + "sig": "" + } + ] +} +``` + +**Replay Provenance:** +- Include feed snapshot digest +- Include resolver policy version +- Store signed attestation in content-addressed storage + +--- + +## 7. Performance Considerations + +### 7.1 Latency Targets + +| Tier | Operation | Target Latency | Notes | +|------|-----------|----------------|-------| +| 1 | Derivative OVAL query | <50ms | In-memory or local DB | +| 2 | Changelog parsing | <100ms | Pre-indexed by package version | +| 3 | Patch hunk matching | <150ms | Content-addressed lookup | +| 4 | Upstream commit mapping | <500ms | May require git fetch (cached) | +| 5 | NVD range check | <50ms | Simple version comparison | + +**Overall P95 Latency Goal:** <200ms for typical case (Tier 1-3) + +### 7.2 Caching Strategy + +**In-Memory Caches:** +- Bug ID CVE mappings: 1h TTL, max 10,000 entries +- Derivative OVAL queries: 5min TTL, max 5,000 entries +- Version comparison results: 10min TTL, max 50,000 entries + +**Persistent Caches:** +- OVAL/CSAF feeds: File-based, refresh every 6h +- Patch hunk signatures: Content-addressed storage (immutable) + +### 7.3 Scalability + +**Concurrency:** +- Parallel tier evaluation within single CVE (Tier 1-3 can run concurrently if needed) +- Bulk CVE scans: Process 100 CVEs in parallel with semaphore limit + +**Database Optimization:** +- Index on (distro, release, package_name, cve_id) +- Partition OVAL/CSAF rules by distro family (rhel, debian, alpine) + +--- + +**End of Design Document** diff --git a/docs/implplan/SPRINT_20251230_001_BE_backport_resolver_TESTS.md b/docs/implplan/SPRINT_20251230_001_BE_backport_resolver_TESTS.md new file mode 100644 index 000000000..0df49fc23 --- /dev/null +++ b/docs/implplan/SPRINT_20251230_001_BE_backport_resolver_TESTS.md @@ -0,0 +1,1039 @@ +# Backport Resolver Tiered Evidence - Test Specification +**Sprint:** SPRINT_20251230_001_BE +**Version:** 1.0 +**Last Updated:** 2025-12-30 + +--- + +## Table of Contents +1. [Test Strategy](#test-strategy) +2. [Unit Test Specifications](#unit-test-specifications) +3. [Integration Test Specifications](#integration-test-specifications) +4. [Golden Test Cases](#golden-test-cases) +5. [Performance Tests](#performance-tests) +6. [Test Data](#test-data) + +--- + +## 1. Test Strategy + +### 1.1 Testing Pyramid + +``` + /\ + / \ E2E Tests (5%) + /\ - Golden CVE scenarios + / \ - Full pipeline (Feedser Concelier VexLens) + /\ + / \ Integration Tests (25%) + /\ - Component interactions + / \ - External API mocking + /\ + / \ Unit Tests (70%) + /\ - Version comparators +/ \ - Tier logic + - Model validation +``` + +### 1.2 Test Coverage Goals + +| Component | Line Coverage | Branch Coverage | Notes | +|-----------|---------------|-----------------|-------| +| BackportStatusService | >90% | >85% | Critical path, all tiers | +| Version Comparators | 100% | 100% | Already achieved | +| DistroMappings | >85% | >80% | Static data + queries | +| BugCveMappingService | >75% | >70% | External API dependency | +| ChangelogParser | >90% | >85% | Regex-heavy, edge cases | +| HunkSigExtractor | >85% | >80% | Function extraction | + +### 1.3 Test Organization + +``` +tests/ + StellaOps.Concelier.BackportProof.Tests/ + Unit/ + BackportStatusServiceTests.cs + DistroMappingsTests.cs + RulePriorityTests.cs + Integration/ + DerivativeMappingIntegrationTests.cs + BugCveMappingIntegrationTests.cs + EndToEndResolverTests.cs + TestData/ + oval_fixtures/ + changelog_fixtures/ + golden_cases.json + + StellaOps.BugTracking.Tests/ + Unit/ + DebianSecurityTrackerClientTests.cs + CompositeBugCveMappingServiceTests.cs + + StellaOps.Feedser.Core.Tests/ + Unit/ + HunkSigExtractorTests.cs + ChangelogParserTests.cs +``` + +--- + +## 2. Unit Test Specifications + +### 2.1 BackportStatusService Unit Tests + +#### Test Suite: VersionComparatorIntegration + +**File:** BackportStatusServiceTests.cs + +```csharp +public class BackportStatusServiceTests +{ + private readonly BackportStatusService _sut; + private readonly Mock _mockRepo; + + [Theory] + [InlineData("1.2.10", "1.2.9", PackageEcosystem.Rpm, 1)] // 1.2.10 > 1.2.9 + [InlineData("1:2.0", "3.0", PackageEcosystem.Rpm, 1)] // Epoch wins + [InlineData("1.0~beta", "1.0", PackageEcosystem.Deb, -1)] // Tilde pre-release + [InlineData("1.2.3_p1-r0", "1.2.3-r0", PackageEcosystem.Alpine, 1)] // Alpine patch level + public async Task VersionComparison_UsesEcosystemSpecificComparer( + string version1, + string version2, + PackageEcosystem ecosystem, + int expectedSign) + { + // Arrange + var package = new PackageInstance( + new PackageKey("testpkg", ecosystem), + version1); + + var rule = new BoundaryRule( + CveId: "CVE-2024-1234", + FixedVersion: version2, + Priority: RulePriority.DistroNativeOval); + + _mockRepo.Setup(r => r.GetRulesAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync([rule]); + + // Act + var verdict = await _sut.EvalPatchedStatusAsync( + new BackportContext("rhel", 9), + package, + "CVE-2024-1234"); + + // Assert + if (expectedSign > 0) + verdict.Status.Should().Be(FixStatus.Fixed); + else + verdict.Status.Should().Be(FixStatus.Vulnerable); + } + + [Fact] + public async Task EpochVersionRelease_ParsedCorrectly_ForRpm() + { + // Test case: 2:7.76.1-26.el9_3.2 vs 7.77.0 + // Despite 7.76 < 7.77, epoch 2 means it's newer + + var package = new PackageInstance( + new PackageKey("curl", PackageEcosystem.Rpm), + "2:7.76.1-26.el9_3.2"); + + var rule = new BoundaryRule( + CveId: "CVE-2024-1234", + FixedVersion: "7.77.0", // No epoch = epoch 0 + Priority: RulePriority.DistroNativeOval); + + _mockRepo.Setup(r => r.GetRulesAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync([rule]); + + // Act + var verdict = await _sut.EvalPatchedStatusAsync( + new BackportContext("rhel", 9), + package, + "CVE-2024-1234"); + + // Assert + verdict.Status.Should().Be(FixStatus.Fixed); // Epoch 2 > epoch 0 + verdict.Confidence.Should().Be(VerdictConfidence.High); + } +} +``` + +--- + +#### Test Suite: RangeRuleEvaluation + +```csharp +[Fact] +public async Task RangeRule_VersionInRange_ReturnsVulnerable() +{ + // Arrange + var package = new PackageInstance( + new PackageKey("zlib", PackageEcosystem.Alpine), + "1.2.11-r3"); + + var rangeRule = new RangeRule( + CveId: "CVE-2024-99999", + AffectedRange: new VersionRange( + MinVersion: "1.2.0", + MinInclusive: true, + MaxVersion: "1.2.12", + MaxInclusive: false), + Priority: RulePriority.NvdRangeHeuristic); + + _mockRepo.Setup(r => r.GetRulesAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync([rangeRule]); + + // Act + var verdict = await _sut.EvalPatchedStatusAsync( + new BackportContext("alpine", 318), + package, + "CVE-2024-99999"); + + // Assert + verdict.Status.Should().Be(FixStatus.Vulnerable); + verdict.Confidence.Should().Be(VerdictConfidence.Low); // Tier 5 + verdict.EvidencePointer.Type.Should().Be("NvdCpeRange"); +} + +[Fact] +public async Task RangeRule_VersionOutOfRange_ReturnsFixed() +{ + var package = new PackageInstance( + new PackageKey("zlib", PackageEcosystem.Alpine), + "1.2.13-r0"); // Above max version + + var rangeRule = new RangeRule( + CveId: "CVE-2024-99999", + AffectedRange: new VersionRange( + MinVersion: "1.2.0", + MinInclusive: true, + MaxVersion: "1.2.12", + MaxInclusive: false), + Priority: RulePriority.NvdRangeHeuristic); + + _mockRepo.Setup(r => r.GetRulesAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync([rangeRule]); + + // Act + var verdict = await _sut.EvalPatchedStatusAsync( + new BackportContext("alpine", 318), + package, + "CVE-2024-99999"); + + // Assert (Note: out of range could mean either fixed OR not affected) + verdict.Status.Should().NotBe(FixStatus.Vulnerable); + verdict.Confidence.Should().Be(VerdictConfidence.Low); +} + +[Fact] +public async Task RangeRule_UnboundedMax_EvaluatesCorrectly() +{ + // Range: [1.0.0, ) all versions >= 1.0.0 are vulnerable + + var package = new PackageInstance( + new PackageKey("testpkg", PackageEcosystem.Rpm), + "2.5.0"); + + var rangeRule = new RangeRule( + CveId: "CVE-2024-1111", + AffectedRange: new VersionRange( + MinVersion: "1.0.0", + MinInclusive: true, + MaxVersion: null, // Unbounded + MaxInclusive: false), + Priority: RulePriority.NvdRangeHeuristic); + + _mockRepo.Setup(r => r.GetRulesAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync([rangeRule]); + + // Act + var verdict = await _sut.EvalPatchedStatusAsync( + new BackportContext("rhel", 9), + package, + "CVE-2024-1111"); + + // Assert + verdict.Status.Should().Be(FixStatus.Vulnerable); +} +``` + +--- + +### 2.2 DistroMappings Unit Tests + +**File:** DistroMappingsTests.cs + +```csharp +public class DistroMappingsTests +{ + [Theory] + [InlineData("rhel", 9, new[] { "almalinux", "rocky", "centos" })] + [InlineData("rhel", 8, new[] { "almalinux", "rocky" })] + [InlineData("ubuntu", 22, new[] { "linuxmint" })] + [InlineData("debian", 12, new[] { "ubuntu" })] + public void FindDerivativesFor_ReturnsExpectedDerivatives( + string distro, + int release, + string[] expectedDerivatives) + { + // Act + var derivatives = DistroMappings.FindDerivativesFor(distro, release); + + // Assert + derivatives.Select(d => d.DerivativeDistro) + .Should().BeEquivalentTo(expectedDerivatives); + } + + [Fact] + public void FindDerivativesFor_NoMatch_ReturnsEmpty() + { + var derivatives = DistroMappings.FindDerivativesFor("gentoo", 2024); + + derivatives.Should().BeEmpty(); + } + + [Theory] + [InlineData(DerivativeConfidence.High, 0.95)] + [InlineData(DerivativeConfidence.Medium, 0.80)] + public void GetConfidenceMultiplier_ReturnsCorrectValue( + DerivativeConfidence confidence, + decimal expectedMultiplier) + { + var multiplier = DistroMappings.GetConfidenceMultiplier(confidence); + + multiplier.Should().Be(expectedMultiplier); + } + + [Fact] + public void Derivatives_OrderedByConfidence_HighFirst() + { + var derivatives = DistroMappings.FindDerivativesFor("rhel", 9) + .ToList(); + + derivatives.Should().AllSatisfy(d => + d.Confidence.Should().Be(DerivativeConfidence.High)); + } +} +``` + +--- + +### 2.3 ChangelogParser Unit Tests + +**File:** ChangelogParserTests.cs + +```csharp +public class ChangelogParserTests +{ + private readonly ChangelogParser _parser = new(); + + [Fact] + public void ParseDebianChangelog_ExtractsBugIds() + { + var changelog = @" +curl (7.88.1-10+deb12u1) bookworm-security; urgency=high + + * Fix CVE-2024-1234: buffer overflow in cookie parsing + Closes: #987654 + + -- Debian Security Mon, 30 Dec 2024 10:00:00 +0000 +"; + + var entries = _parser.ParseDebian(changelog); + + entries.Should().ContainSingle(); + var entry = entries.First(); + + entry.CveIds.Should().Contain("CVE-2024-1234"); + entry.BugIds.Should().Contain(new BugId("Debian", "987654")); + } + + [Fact] + public void ParseRpmChangelog_ExtractsRhbzBugIds() + { + var changelog = @" +* Mon Dec 30 2024 Red Hat Security - 7.76.1-26.el9_3.2 +- Fix CVE-2024-5678 (RHBZ#123456) +- Backport upstream fix +"; + + var entries = _parser.ParseRpm(changelog); + + entries.Should().ContainSingle(); + var entry = entries.First(); + + entry.CveIds.Should().Contain("CVE-2024-5678"); + entry.BugIds.Should().Contain(new BugId("RHBZ", "123456")); + } + + [Fact] + public void ParseChangelog_MultipleCvesAndBugs_ExtractsAll() + { + var changelog = @" +curl (7.88.1-10+deb12u2) bookworm-security; urgency=high + + * Security fixes: + - CVE-2024-1111: denial of service (Closes: #111111) + - CVE-2024-2222: information disclosure (Closes: #222222) + - CVE-2024-3333: code execution (LP: #333333) + + -- Maintainer Mon, 30 Dec 2024 12:00:00 +0000 +"; + + var entries = _parser.ParseDebian(changelog); + + var entry = entries.First(); + + entry.CveIds.Should().HaveCount(3); + entry.BugIds.Should().HaveCount(3); + entry.BugIds.Should().Contain(new BugId("Debian", "111111")); + entry.BugIds.Should().Contain(new BugId("Debian", "222222")); + entry.BugIds.Should().Contain(new BugId("Launchpad", "333333")); + } + + [Fact] + public void ParseChangelog_NoBugIds_ReturnsEmptyBugList() + { + var changelog = @" +curl (7.88.1-10+deb12u1) bookworm; urgency=low + + * Non-security update + * Performance improvements + + -- Maintainer Mon, 30 Dec 2024 10:00:00 +0000 +"; + + var entries = _parser.ParseDebian(changelog); + + var entry = entries.First(); + + entry.BugIds.Should().BeEmpty(); + } +} +``` + +--- + +### 2.4 HunkSigExtractor Unit Tests + +**File:** HunkSigExtractorTests.cs + +```csharp +public class HunkSigExtractorTests +{ + private readonly HunkSigExtractor _extractor = new(); + + [Fact] + public void ExtractFunctions_CFunctionSignature_Extracted() + { + var patchContent = @" +--- a/ssl/ssl_lib.c ++++ b/ssl/ssl_lib.c +@@ -1234,7 +1234,7 @@ static int ssl_verify(SSL *ssl, X509 *cert) + { + if (!cert) { +- return 0; ++ return -1; // CVE-2024-1234 fix + } + return 1; + } +"; + + var hunks = _extractor.ExtractHunkSigs(patchContent); + + hunks.Should().ContainSingle(); + hunks.First().AffectedFunctions.Should().Contain("ssl_verify"); + } + + [Fact] + public void ExtractFunctions_PythonFunctionSignature_Extracted() + { + var patchContent = @" +--- a/lib/parser.py ++++ b/lib/parser.py +@@ -42,7 +42,7 @@ def parse_cookie(cookie_str): + if not cookie_str: +- return None ++ raise ValueError(\"Empty cookie\") # CVE-2024-5678 fix + return Cookie(cookie_str) +"; + + var hunks = _extractor.ExtractHunkSigs(patchContent); + + hunks.First().AffectedFunctions.Should().Contain("parse_cookie"); + } + + [Fact] + public void ExtractFunctions_GoMethodSignature_Extracted() + { + var patchContent = @" +--- a/pkg/server/handler.go ++++ b/pkg/server/handler.go +@@ -88,7 +88,7 @@ func (h *Handler) ProcessRequest(req *Request) error { + if req == nil { +- return nil ++ return errors.New(\"nil request\") // CVE-2024-9999 fix + } + return h.process(req) + } +"; + + var hunks = _extractor.ExtractHunkSigs(patchContent); + + hunks.First().AffectedFunctions.Should().Contain("ProcessRequest"); + } + + [Fact] + public void ExtractFunctions_MultipleFunctions_AllExtracted() + { + var patchContent = @" +--- a/crypto/rsa.c ++++ b/crypto/rsa.c +@@ -100,10 +100,10 @@ static int rsa_encrypt(RSA *rsa, const uint8_t *in, uint8_t *out) + { + // ... encryption logic + } + + static int rsa_decrypt(RSA *rsa, const uint8_t *in, uint8_t *out) + { +- // Vulnerable code ++ // Fixed code (CVE-2024-1234) + } +"; + + var hunks = _extractor.ExtractHunkSigs(patchContent); + + var functions = hunks.SelectMany(h => h.AffectedFunctions).Distinct(); + functions.Should().Contain("rsa_encrypt"); + functions.Should().Contain("rsa_decrypt"); + } +} +``` + +--- + +## 3. Integration Test Specifications + +### 3.1 Derivative Distro Mapping Integration + +**File:** DerivativeMappingIntegrationTests.cs + +```csharp +public class DerivativeMappingIntegrationTests +{ + private readonly BackportStatusService _sut; + private readonly InMemoryFixRuleRepository _repo; + + [Fact] + public async Task DerivativeMapping_AlmaLinuxOvalForRocky_SuccessWithConfidencePenalty() + { + // Arrange: AlmaLinux has OVAL, Rocky does not + var almaRule = new BoundaryRule( + CveId: "CVE-2024-1234", + FixedVersion: "7.76.1-26.el9_3.2", + Priority: RulePriority.DistroNativeOval, + Confidence: 1.0m); + + _repo.AddRule("almalinux", 9, "curl", almaRule); + + var package = new PackageInstance( + new PackageKey("curl", PackageEcosystem.Rpm), + "7.76.1-26.el9_3.2"); + + // Act: Query for Rocky 9 (should fallback to AlmaLinux) + var verdict = await _sut.EvalPatchedStatusAsync( + new BackportContext("rocky", 9), + package, + "CVE-2024-1234"); + + // Assert + verdict.Status.Should().Be(FixStatus.Fixed); + verdict.Confidence.Should().BeApproximately(VerdictConfidence.High, 0.05m); + verdict.EvidencePointer.Uri.Should().Contain("derivative:almalinuxrocky"); + } + + [Fact] + public async Task DerivativeMapping_NoDerivativeOval_FallsBackToNextTier() + { + // Arrange: No OVAL for Rocky or any derivative + + var package = new PackageInstance( + new PackageKey("curl", PackageEcosystem.Rpm), + "7.76.1-26.el9_3.2"); + + // Act + var verdict = await _sut.EvalPatchedStatusAsync( + new BackportContext("rocky", 9), + package, + "CVE-2024-1234"); + + // Assert: Should try Tier 2 (changelog), Tier 3 (patches), etc. + verdict.Status.Should().NotBe(FixStatus.Fixed); // Or could be Fixed from other tier + verdict.EvidencePointer.Type.Should().NotBe("OvalAdvisory"); + } +} +``` + +--- + +### 3.2 BugCVE Mapping Integration + +**File:** BugCveMappingIntegrationTests.cs + +```csharp +public class BugCveMappingIntegrationTests +{ + private readonly Mock _httpFactory; + private readonly DebianSecurityTrackerClient _client; + + [Fact] + public async Task LookupCvesAsync_DebianBug_ReturnsMappedCves() + { + // Arrange + var mockHttp = new MockHttpMessageHandler(); + mockHttp.When("https://security-tracker.debian.org/tracker/data/json") + .Respond("application/json", @" +{ + ""987654"": { + ""description"": ""curl: buffer overflow"", + ""cves"": [""CVE-2024-1234"", ""CVE-2024-5678""] + } +}"); + + _httpFactory.Setup(f => f.CreateClient(It.IsAny())) + .Returns(new HttpClient(mockHttp)); + + var client = new DebianSecurityTrackerClient(_httpFactory.Object, new MemoryCache(new MemoryCacheOptions())); + + // Act + var cves = await client.LookupCvesAsync(new BugId("Debian", "987654")); + + // Assert + cves.Should().Contain("CVE-2024-1234"); + cves.Should().Contain("CVE-2024-5678"); + } + + [Fact] + public async Task LookupCvesAsync_CachesResults() + { + // First call fetch from API + var cves1 = await _client.LookupCvesAsync(new BugId("Debian", "987654")); + + // Second call should use cache (no HTTP request) + var cves2 = await _client.LookupCvesAsync(new BugId("Debian", "987654")); + + cves1.Should().BeEquivalentTo(cves2); + // Verify only one HTTP call was made + } + + [Fact] + public async Task LookupCvesAsync_ApiFailure_ReturnsEmpty() + { + // Arrange: API returns 500 error + var mockHttp = new MockHttpMessageHandler(); + mockHttp.When("*").Respond(HttpStatusCode.InternalServerError); + + _httpFactory.Setup(f => f.CreateClient(It.IsAny())) + .Returns(new HttpClient(mockHttp)); + + var client = new DebianSecurityTrackerClient(_httpFactory.Object, new MemoryCache(new MemoryCacheOptions())); + + // Act + var cves = await client.LookupCvesAsync(new BugId("Debian", "987654")); + + // Assert + cves.Should().BeEmpty(); // Graceful degradation + } +} +``` + +--- + +## 4. Golden Test Cases + +### 4.1 Test Case 1: OpenSSL on Rocky 9 with Derivative OVAL + +**File:** golden_cases.json + +```json +{ + "testCaseId": "GOLDEN-001", + "name": "CVE-2024-26130: OpenSSL on Rocky Linux 9 with AlmaLinux OVAL", + "scenario": "Backported fix with derivative OVAL evidence", + "inputs": { + "cve": "CVE-2024-26130", + "package": { + "name": "openssl", + "version": "3.0.7-24.el9", + "ecosystem": "rpm" + }, + "distro": { + "name": "rocky", + "release": 9 + }, + "availableEvidence": { + "almalinuxOval": { + "advisoryId": "ALSA-2024-1234", + "fixedVersion": "3.0.7-24.el9", + "severity": "important" + } + } + }, + "expected": { + "status": "fixed", + "confidence": "high", + "confidenceScore": 0.95, + "tier": 1, + "evidenceType": "OvalAdvisory", + "evidenceUri": "derivative:almalinuxrocky:oval:ALSA-2024-1234" + } +} +``` + +### 4.2 Test Case 2: curl on Debian with Bug ID + +```json +{ + "testCaseId": "GOLDEN-002", + "name": "CVE-2023-12345: curl on Debian 12 with bug ID mapping", + "scenario": "Changelog evidence with Debian bug ID CVE mapping", + "inputs": { + "cve": "CVE-2023-12345", + "package": { + "name": "curl", + "version": "7.88.1-10+deb12u1", + "ecosystem": "deb" + }, + "distro": { + "name": "debian", + "release": 12 + }, + "availableEvidence": { + "changelog": "curl (7.88.1-10+deb12u1) bookworm-security; urgency=high\n * Fix buffer overflow (Closes: #987654)\n", + "bugMapping": { + "987654": ["CVE-2023-12345"] + } + } + }, + "expected": { + "status": "fixed", + "confidence": "medium", + "confidenceScore": 0.75, + "tier": 2, + "evidenceType": "DebianChangelog", + "evidenceUri": "changelog:debian:curl:7.88.1-10+deb12u1#bug:987654" + } +} +``` + +### 4.3 Test Case 3: zlib with NVD Range Fallback + +```json +{ + "testCaseId": "GOLDEN-003", + "name": "CVE-2024-99999: zlib on Alpine with NVD range only", + "scenario": "Fallback to NVD range heuristic (Tier 5)", + "inputs": { + "cve": "CVE-2024-99999", + "package": { + "name": "zlib", + "version": "1.2.11-r3", + "ecosystem": "alpine" + }, + "distro": { + "name": "alpine", + "release": 318 + }, + "availableEvidence": { + "nvdRange": { + "minVersion": "1.2.0", + "minInclusive": true, + "maxVersion": "1.2.12", + "maxInclusive": false + } + } + }, + "expected": { + "status": "vulnerable", + "confidence": "low", + "confidenceScore": 0.40, + "tier": 5, + "evidenceType": "NvdCpeRange", + "evidenceUri": "nvd:cve/CVE-2024-99999/cpe:2.3:a:zlib:zlib:*" + } +} +``` + +### 4.4 Running Golden Tests + +```csharp +public class GoldenTestRunner +{ + private readonly BackportStatusService _sut; + + [Theory] + [MemberData(nameof(LoadGoldenCases))] + public async Task GoldenCase_ProducesExpectedVerdict(GoldenTestCase testCase) + { + // Arrange + SetupEvidence(testCase.Inputs.AvailableEvidence); + + var package = new PackageInstance( + new PackageKey(testCase.Inputs.Package.Name, testCase.Inputs.Package.Ecosystem), + testCase.Inputs.Package.Version); + + var context = new BackportContext( + testCase.Inputs.Distro.Name, + testCase.Inputs.Distro.Release); + + // Act + var verdict = await _sut.EvalPatchedStatusAsync( + context, + package, + testCase.Inputs.Cve); + + // Assert + verdict.Status.ToString().ToLower().Should().Be(testCase.Expected.Status); + verdict.Confidence.ToString().ToLower().Should().Be(testCase.Expected.Confidence); + verdict.EvidencePointer.Type.Should().Be(testCase.Expected.EvidenceType); + verdict.EvidencePointer.Uri.Should().Contain(testCase.Expected.EvidenceUri); + + // Log for audit trail + _testOutputHelper.WriteLine($"PASS: {testCase.TestCaseId} - {testCase.Name}"); + _testOutputHelper.WriteLine($" Tier: {testCase.Expected.Tier}, Confidence: {verdict.Confidence}"); + } + + public static IEnumerable LoadGoldenCases() + { + var json = File.ReadAllText("TestData/golden_cases.json"); + var cases = JsonSerializer.Deserialize>(json); + return cases.Select(c => new object[] { c }); + } +} +``` + +--- + +## 5. Performance Tests + +### 5.1 Latency Benchmarks + +**File:** BackportResolverBenchmarks.cs + +```csharp +[MemoryDiagnoser] +public class BackportResolverBenchmarks +{ + private BackportStatusService _sut; + + [GlobalSetup] + public void Setup() + { + // Initialize with realistic data + } + + [Benchmark] + [BenchmarkCategory("Tier1")] + public async Task Tier1_DerivativeOval() + { + // Measure Tier 1 latency: derivative OVAL lookup + return await _sut.EvalPatchedStatusAsync( + new BackportContext("rocky", 9), + new PackageInstance(new PackageKey("curl", PackageEcosystem.Rpm), "7.76.1-26.el9"), + "CVE-2024-1234"); + } + + [Benchmark] + [BenchmarkCategory("Tier5")] + public async Task Tier5_NvdRange() + { + // Measure Tier 5 latency: NVD range evaluation + return await _sut.EvalPatchedStatusAsync( + new BackportContext("alpine", 318), + new PackageInstance(new PackageKey("zlib", PackageEcosystem.Alpine), "1.2.11-r3"), + "CVE-2024-99999"); + } +} +``` + +**Acceptance Criteria:** + +| Benchmark | Target Latency | Max Allocations | +|-----------|----------------|-----------------| +| Tier1_DerivativeOval | <50ms (P95) | <5 KB | +| Tier2_Changelog | <100ms (P95) | <10 KB | +| Tier5_NvdRange | <50ms (P95) | <3 KB | + +--- + +### 5.2 Bulk Scan Test + +```csharp +[Fact] +public async Task BulkScan_10000Packages_CompletesWithin5Minutes() +{ + // Arrange + var packages = GenerateTestPackages(10_000); + var cves = GenerateTestCves(100); + var stopwatch = Stopwatch.StartNew(); + + // Act + var results = await Task.WhenAll( + packages.SelectMany(p => + cves.Select(cve => + _sut.EvalPatchedStatusAsync( + new BackportContext("ubuntu", 22), + p, + cve)))); + + stopwatch.Stop(); + + // Assert + stopwatch.Elapsed.Should().BeLessThan(TimeSpan.FromMinutes(5)); + results.Should().HaveCount(10_000 * 100); +} +``` + +--- + +## 6. Test Data + +### 6.1 OVAL Fixture Example + +**File:** TestData/oval_fixtures/alma_9_curl_2024_1234.xml + +```xml + + + + + ALSA-2024:1234: curl security update (Important) + + AlmaLinux 9 + + + + + + + + + +``` + +### 6.2 Changelog Fixture Example + +**File:** TestData/changelog_fixtures/debian_curl_12u1.txt + +``` +curl (7.88.1-10+deb12u1) bookworm-security; urgency=high + + * SECURITY UPDATE: + - Fix CVE-2024-1234: buffer overflow in cookie handling + - Fix CVE-2024-5678: denial of service in HTTP/2 parser + - Closes: #987654, #987655 + * Cherry-pick upstream commits: + - a1b2c3d: Validate cookie length before copy + - e4f5g6h: Add bounds check to HTTP/2 header parsing + + -- Debian Security Mon, 30 Dec 2024 10:00:00 +0000 + +curl (7.88.1-10) bookworm; urgency=medium + + * New upstream release + * Update debian/patches for 7.88 series + + -- Maintainer Fri, 15 Dec 2024 14:00:00 +0000 +``` + +### 6.3 Patch Fixture Example + +**File:** TestData/patch_fixtures/curl_cve_2024_1234.patch + +```diff +From a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 Mon Sep 17 00:00:00 2001 +From: Security Team +Date: Mon, 30 Dec 2024 10:00:00 +0000 +Subject: [PATCH] Fix CVE-2024-1234: buffer overflow in cookie parsing + +Validate cookie length before memcpy to prevent buffer overflow. + +CVE-2024-1234 +--- + lib/cookie.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/lib/cookie.c b/lib/cookie.c +index a1b2c3d..e4f5g6h 100644 +--- a/lib/cookie.c ++++ b/lib/cookie.c +@@ -234,7 +234,10 @@ static int parse_cookie(struct Cookie *cookie, const char *str) + { + size_t len = strlen(str); +- memcpy(cookie->value, str, len); ++ if(len >= sizeof(cookie->value)) { ++ return -1; /* Cookie too long */ ++ } ++ memcpy(cookie->value, str, len + 1); + cookie->value[len] = '\0'; + return 0; + } +-- +2.34.1 +``` + +--- + +## 7. Continuous Integration + +### 7.1 CI Pipeline Configuration + +```yaml +name: Backport Resolver Tests + +on: [push, pull_request] + +jobs: + unit-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-dotnet@v3 + with: + dotnet-version: '8.0.x' + - run: dotnet test tests/StellaOps.Concelier.BackportProof.Tests/Unit --logger "trx;LogFileName=unit-tests.trx" + - run: dotnet test tests/StellaOps.BugTracking.Tests --logger "trx;LogFileName=bug-tracking-tests.trx" + - name: Upload test results + uses: actions/upload-artifact@v3 + with: + name: test-results + path: '**/*.trx' + + integration-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: dotnet test tests/StellaOps.Concelier.BackportProof.Tests/Integration --logger "trx" + + golden-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: dotnet test tests/StellaOps.Concelier.BackportProof.Tests --filter "Category=Golden" --logger "trx" + + performance-tests: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v3 + - run: dotnet run --project tests/Benchmarks/BackportResolverBenchmarks.csproj -c Release + - name: Compare with baseline + run: | + dotnet run --project tools/benchmark-comparer.csproj baseline.json current.json +``` + +--- + +**End of Test Specification** diff --git a/docs/implplan/SPRINT_20251230_001_BE_backport_resolver_tiered_evidence.md b/docs/implplan/SPRINT_20251230_001_BE_backport_resolver_tiered_evidence.md new file mode 100644 index 000000000..8e58456cb --- /dev/null +++ b/docs/implplan/SPRINT_20251230_001_BE_backport_resolver_tiered_evidence.md @@ -0,0 +1,906 @@ +# SPRINT_20251230_001_BE_backport_resolver_tiered_evidence + +## Sprint Metadata + +| Field | Value | +|-------|-------| +| **Sprint ID** | SPRINT_20251230_001_BE | +| **Topic** | Tiered Evidence Backport Resolver Enhancement | +| **Module** | Concelier.BackportProof, Concelier.SourceIntel, Feedser.Core | +| **Working Directory** | `src/Concelier/__Libraries/StellaOps.Concelier.BackportProof/` | +| **Priority** | P0 - Critical | +| **Estimated Effort** | 5 days | +| **Dependencies** | StellaOps.VersionComparison, StellaOps.Concelier.Merge | + +--- + +## Executive Summary + +This sprint addresses critical gaps in the backport patch resolver that cause false positives/negatives when determining if a CVE is fixed in Linux distribution packages. The current implementation uses string comparison for version matching and lacks derivative distro mapping, resulting in incorrect vulnerability assessments. + +### Key Deliverables + +1. Wire ecosystem-specific version comparators into BackportStatusService +2. Implement RangeRule evaluation for NVD fallback (Tier 5) +3. Add derivative distro mapping for OVAL/CSAF cross-referencing (Tier 1) +4. Enhance changelog parsing with bug ID → CVE mapping (Tier 2) +5. Extract affected functions from patch context (Tier 3/4) +6. Align confidence scoring to five-tier evidence hierarchy + +--- + +## Background & Problem Statement + +### Current State + +The `BackportStatusService` uses `string.Compare()` for version comparison: + +```csharp +// BackportStatusService.cs:198 +var isPatched = string.Compare(package.InstalledVersion, fixedVersion, StringComparison.Ordinal) >= 0; +``` + +**Failures:** +- `1.2.10` vs `1.2.9` → returns `-1` (should be `+1`) +- `1:2.0` vs `3.0` (epoch) → completely wrong +- `1.2.3~beta` vs `1.2.3` (tilde) → wrong order + +### Proposed Five-Tier Evidence Hierarchy + +| Tier | Evidence Source | Confidence | Priority | +|------|-----------------|------------|----------| +| 1 | Derivative OVAL/CSAF (same release) | 0.95-0.98 | 100 | +| 2 | Changelog CVE markers | 0.75-0.85 | 85 | +| 3 | Source patch files (HunkSig) | 0.80-0.95 | 70 | +| 4 | Upstream commit mapping | 0.55-0.85 | 55 | +| 5 | NVD version ranges (fallback) | Low only | 20 | + +--- + +## Delivery Tracker + +### Phase 1: Version Comparator Integration (P0) + +| Task ID | Description | Status | Assignee | Notes | +|---------|-------------|--------|----------|-------| +| BP-101 | Create IVersionComparatorFactory interface | TODO | | DI registration | +| BP-102 | Wire comparators into BackportStatusService | TODO | | RPM, DEB, APK | +| BP-103 | Update EvaluateBoundaryRules with proof lines | TODO | | Audit trail | +| BP-104 | Unit tests for version comparison edge cases | TODO | | Golden datasets | +| BP-105 | Integration test: epoch handling | TODO | | `1:2.0` vs `3.0` | + +### Phase 2: RangeRule Implementation (P0) + +| Task ID | Description | Status | Assignee | Notes | +|---------|-------------|--------|----------|-------| +| BP-201 | Implement EvaluateRangeRules with comparators | TODO | | Min/max bounds | +| BP-202 | Handle inclusive/exclusive boundaries | TODO | | `[` vs `(` | +| BP-203 | Add Low confidence for NVD-sourced ranges | TODO | | Tier 5 | +| BP-204 | Unit tests for range edge cases | TODO | | Open/closed | +| BP-205 | Integration test: NVD fallback path | TODO | | E2E flow | + +### Phase 3: Derivative Distro Mapping (P1) + +| Task ID | Description | Status | Assignee | Notes | +|---------|-------------|--------|----------|-------| +| BP-301 | Create DistroDerivativeMapping model | TODO | | Canonical/derivative | +| BP-302 | Add RHEL ↔ Alma/Rocky/CentOS mappings | TODO | | Major release | +| BP-303 | Add Ubuntu ↔ LinuxMint mappings | TODO | | | +| BP-304 | Add Debian ↔ Ubuntu mappings | TODO | | | +| BP-305 | Integrate into rule fetching with confidence penalty | TODO | | 0.95x multiplier | +| BP-306 | Unit tests for derivative lookup | TODO | | | +| BP-307 | Integration test: cross-distro OVAL | TODO | | RHEL→Rocky | + +### Phase 4: Bug ID → CVE Mapping (P1) + +| Task ID | Description | Status | Assignee | Notes | +|---------|-------------|--------|----------|-------| +| BP-401 | Add Debian bug regex extraction | TODO | | `Closes: #123` | +| BP-402 | Add RHBZ bug regex extraction | TODO | | `RHBZ#123` | +| BP-403 | Add Launchpad bug regex extraction | TODO | | `LP: #123` | +| BP-404 | Create IBugCveMappingService interface | TODO | | Async lookup | +| BP-405 | Implement DebianSecurityTrackerClient | TODO | | API client | +| BP-406 | Implement RedHatErrataClient | TODO | | API client | +| BP-407 | Cache layer for bug→CVE mappings | TODO | | 24h TTL | +| BP-408 | Unit tests for bug ID extraction | TODO | | Regex patterns | +| BP-409 | Integration test: Debian tracker lookup | TODO | | Live API | + +### Phase 5: Affected Functions Extraction (P2) + +| Task ID | Description | Status | Assignee | Notes | +|---------|-------------|--------|----------|-------| +| BP-501 | Create function signature regex patterns | TODO | | C, Go, Python | +| BP-502 | Implement ExtractFunctionsFromContext | TODO | | In HunkSigExtractor | +| BP-503 | Add C/C++ function pattern | TODO | | `void foo(` | +| BP-504 | Add Go function pattern | TODO | | `func (r *R) M(` | +| BP-505 | Add Python function pattern | TODO | | `def foo(` | +| BP-506 | Add Rust function pattern | TODO | | `fn foo(` | +| BP-507 | Unit tests for function extraction | TODO | | Multi-language | +| BP-508 | Enable fuzzy function matching in Tier 3/4 | TODO | | Similarity score | + +### Phase 6: Confidence Tier Alignment (P2) + +| Task ID | Description | Status | Assignee | Notes | +|---------|-------------|--------|----------|-------| +| BP-601 | Expand RulePriority enum | TODO | | 9 levels | +| BP-602 | Update BackportStatusService priority logic | TODO | | Tier ordering | +| BP-603 | Add confidence multipliers per tier | TODO | | | +| BP-604 | Update EvidencePointer with TierSource | TODO | | Audit | +| BP-605 | Unit tests for tier precedence | TODO | | | + +--- + +## Decisions & Risks + +### Decisions Made + +| ID | Decision | Rationale | Date | +|----|----------|-----------|------| +| D-001 | Use existing VersionComparison library | Already implements rpmvercmp, dpkg, apk semantics | 2025-12-30 | +| D-002 | Derivative confidence penalty 0.95x (High) / 0.80x (Medium) | Same ABI rebuilds vs partial compatibility | 2025-12-30 | +| D-003 | Bug→CVE cache TTL 24 hours | Balance freshness vs API rate limits | 2025-12-30 | + +### Open Risks + +| ID | Risk | Mitigation | Status | +|----|------|------------|--------| +| R-001 | Debian Security Tracker API rate limits | Implement exponential backoff + cache | OPEN | +| R-002 | Function extraction may produce false positives | Add confidence penalty for fuzzy matches | OPEN | +| R-003 | Derivative mappings may drift across major releases | Version-specific mapping table | OPEN | + +--- + +## Acceptance Criteria + +### P0 Tasks (Must complete) + +- [ ] `BackportStatusService` uses proper version comparators for all ecosystems +- [ ] `RangeRule` evaluation returns correct verdicts with Low confidence +- [ ] All existing tests pass +- [ ] New golden tests for version edge cases + +### P1 Tasks (Should complete) + +- [ ] Derivative distro mapping works for RHEL family +- [ ] Bug ID extraction finds Debian/RHBZ/LP references +- [ ] Bug→CVE mapping lookup is cached + +### P2 Tasks (Nice to have) + +- [ ] Function extraction works for C, Go, Python, Rust +- [ ] Confidence tiers aligned to five-tier hierarchy + +--- + +## Test Strategy + +### Unit Tests + +| Area | Test File | Coverage Target | +|------|-----------|-----------------| +| Version comparison | `BackportStatusServiceVersionTests.cs` | All ecosystems | +| Range evaluation | `BackportStatusServiceRangeTests.cs` | Boundary conditions | +| Derivative mapping | `DistroDerivativeMappingTests.cs` | All supported distros | +| Bug ID extraction | `ChangelogBugIdExtractionTests.cs` | Regex patterns | +| Function extraction | `HunkSigFunctionExtractionTests.cs` | Multi-language | + +### Integration Tests + +| Scenario | Test File | External Dependencies | +|----------|-----------|----------------------| +| Cross-distro OVAL | `CrossDistroOvalIntegrationTests.cs` | None (fixtures) | +| Bug→CVE lookup | `BugCveMappingIntegrationTests.cs` | Debian Tracker API | +| Full resolver flow | `BackportResolverE2ETests.cs` | PostgreSQL (Testcontainers) | + +### Golden Datasets + +Location: `src/__Tests/__Datasets/backport-resolver/` + +| Dataset | Purpose | +|---------|---------| +| `rpm-version-edge-cases.json` | Epoch, tilde, release variations | +| `deb-version-edge-cases.json` | Epoch, revision, ubuntu suffixes | +| `apk-version-edge-cases.json` | Pre-release suffixes, pkgrel | +| `cross-distro-oval-fixtures/` | RHEL/Rocky/Alma advisory samples | +| `changelog-with-bugids/` | Debian/RPM changelogs with bug refs | + +--- + +## Execution Log + +| Date | Event | Details | +|------|-------|---------| +| 2025-12-30 | Sprint created | Initial planning and gap analysis | + +--- + +## References + +- `src/Concelier/__Libraries/StellaOps.Concelier.BackportProof/Services/BackportStatusService.cs` +- `src/Concelier/__Libraries/StellaOps.Concelier.BackportProof/Models/FixRuleModels.cs` +- `src/Concelier/__Libraries/StellaOps.Concelier.Merge/Comparers/RpmVersionComparer.cs` +- `src/Concelier/__Libraries/StellaOps.Concelier.Merge/Comparers/ApkVersionComparer.cs` +- `src/Concelier/__Libraries/StellaOps.Concelier.SourceIntel/ChangelogParser.cs` +- `src/Feedser/StellaOps.Feedser.Core/HunkSigExtractor.cs` +- `src/VexLens/StellaOps.VexLens/Consensus/VexConsensusEngine.cs` +**Key Improvements:** +- Wire existing version comparators (RpmVersionComparer, ApkVersionComparer, DebianVersionComparer) into BackportStatusService +- Implement NVD range evaluation (Tier 5 fallback) +- Add derivative distro mapping (RHELAlma/Rocky, UbuntuMint) for Tier 1 evidence +- Extend changelog parser to extract bug IDs and map to CVEs +- Extract function signatures from patch hunks for better matching +- Align confidence scoring with 5-tier evidence hierarchy + +**Impact:** +- Eliminates false positives from incorrect version comparisons (e.g., "1.2.10" < "1.2.9") +- Enables cross-distro evidence sharing (e.g., use AlmaLinux OVAL for RHEL) +- Provides auditable, signed VEX statements with evidence trails +- Reduces manual verification workload by 60-80% + +--- + +## Problem Statement + +### Current Implementation Gaps + +| Gap ID | Description | Severity | Current Behavior | Desired Behavior | +|--------|-------------|----------|------------------|------------------| +| GAP-001 | String-based version comparison | **CRITICAL** | "1.2.10" < "1.2.9" returns true | Use ecosystem-specific comparers (EVR, dpkg, apk) | +| GAP-002 | RangeRule returns Unknown | **CRITICAL** | NVD ranges ignored, always Unknown | Evaluate ranges with proper version semantics | +| GAP-003 | No derivative distro mapping | **HIGH** | AlmaLinux OVAL unused for RHEL scans | Map RHELAlma/Rocky, UbuntuMint with confidence | +| GAP-004 | Bug IDCVE mapping missing | **HIGH** | Only direct CVE mentions detected | Extract Debian/RHBZ/LP bug IDs, map to CVEs | +| GAP-005 | AffectedFunctions not extracted | **MEDIUM** | Hunk matching relies only on content hash | Extract C/Python/Go function signatures for fuzzy match | +| GAP-006 | Confidence tiers misaligned | **MEDIUM** | Priority values don't match evidence quality | Align with 5-tier hierarchy (Tier 1=High, Tier 5=Low) | + +### Real-World Example + +**Scenario:** CVE-2024-1234 in curl on Rocky Linux 9 + +**Current behavior:** +``` +- Installed: curl-7.76.1-26.el9_3.2 +- NVD says: "Fixed in 7.77.0" +- String comparison: "7.76.1-26.el9_3.2" < "7.77.0" **VULNERABLE** (WRONG!) +``` + +**Root cause:** Red Hat backported the fix to 7.76.1-26, but string comparison doesn't understand epoch-version-release semantics. + +**Correct behavior (after sprint):** +``` +1. Check AlmaLinux OVAL (Tier 1): Found fix in curl-7.76.1-26.el9_3.2 +2. Map AlmaRocky (High confidence, same ABI) +3. Verdict: **FIXED** , Confidence: High, Evidence: [Alma OVAL advisory ALSA-2024-1234] +``` + +--- + +## 5-Tier Evidence Hierarchy (Target Architecture) + +`mermaid +graph TD + A[CVE + Package + Distro] --> B{Tier 1: Derivative OVAL/CSAF} + B -->|Found| C[Verdict: FIXED/VULNERABLE
Confidence: High 0.95-0.98] + B -->|Not Found| D{Tier 2: Changelog Markers} + D -->|CVE Match| E[Verdict: FIXED
Confidence: High 0.85] + D -->|Bug ID Match| F[Verdict: FIXED
Confidence: Medium 0.75] + D -->|Not Found| G{Tier 3: Source Patch Files} + G -->|Exact Hunk Hash| H[Verdict: FIXED
Confidence: Medium-High 0.90] + G -->|Fuzzy Function Match| I[Verdict: FIXED
Confidence: Medium 0.70] + G -->|Not Found| J{Tier 4: Upstream Commit Mapping} + J -->|100% Hunk Parity| K[Verdict: FIXED
Confidence: Medium 0.80] + J -->|Partial Match| L[Verdict: FIXED
Confidence: Medium-Low 0.60] + J -->|Not Found| M{Tier 5: NVD Range Fallback} + M -->|In Range| N[Verdict: VULNERABLE
Confidence: Low 0.40] + M -->|Out of Range| O[Verdict: FIXED
Confidence: Low 0.50] + M -->|No Data| P[Verdict: UNKNOWN
Confidence: Low 0.30] +` + +--- + +## Sprint Tasks + +### Phase 1: Foundation (P0 - Critical Path) + +#### Task 1.1: Wire Version Comparators into BackportStatusService +- **File:** src/Concelier/__Libraries/StellaOps.Concelier.BackportProof/Services/BackportStatusService.cs +- **Effort:** 2h +- **Dependencies:** StellaOps.Concelier.Merge.Comparers +- **Acceptance Criteria:** + - [ ] Add IReadOnlyDictionary field + - [ ] Inject comparators in constructor (RPM, Debian, Alpine, Conda) + - [ ] Replace string.Compare() in EvaluateBoundaryRules() with comparator.CompareWithProof() + - [ ] Add fallback StringVersionComparer for unknown ecosystems + - [ ] Unit test: "1.2.10" > "1.2.9" for RPM/Deb/Alpine + - [ ] Unit test: Epoch handling "1:2.0" > "3.0" + - [ ] Unit test: Tilde pre-releases "1.2.3~beta" < "1.2.3" + +**Code Snippet:** +```csharp +// BackportStatusService.cs +private readonly IReadOnlyDictionary _comparators; + +public BackportStatusService( + IFixRuleRepository ruleRepository, + IRpmVersionComparer rpmComparer, + IDebianVersionComparer debComparer, + IApkVersionComparer apkComparer) +{ + _comparators = new Dictionary + { + [PackageEcosystem.Rpm] = rpmComparer, + [PackageEcosystem.Deb] = debComparer, + [PackageEcosystem.Alpine] = apkComparer, + }.ToFrozenDictionary(); +} + +private BackportVerdict EvaluateBoundaryRules(...) +{ + var comparator = _comparators.GetValueOrDefault( + package.Key.Ecosystem, + StringVersionComparer.Instance); + + var result = comparator.CompareWithProof( + package.InstalledVersion, + fixedVersion); + + var isPatched = result.Result >= 0; + // ... rest of logic +} +``` + +--- + +#### Task 1.2: Implement RangeRule Evaluation (Tier 5) +- **File:** BackportStatusService.cs::EvaluateRangeRules() +- **Effort:** 3h +- **Acceptance Criteria:** + - [ ] Evaluate AffectedRange.MinVersion and MaxVersion with inclusive/exclusive bounds + - [ ] Return FixStatus.Vulnerable if in range, FixStatus.Fixed if out of range + - [ ] Set VerdictConfidence.Low for all Tier 5 decisions + - [ ] Add evidence pointer to NVD CPE/range definition + - [ ] Handle null min/max (unbounded ranges) + - [ ] Unit test: CVE-2024-1234 with range [1.0.0, 2.0.0) versions 1.5.0 (vuln), 2.0.1 (fixed) + +**Code Snippet:** +```csharp +private BackportVerdict EvaluateRangeRules( + CveId cve, + PackageInstance package, + IReadOnlyList rules) +{ + var comparator = _comparators.GetValueOrDefault( + package.Key.Ecosystem, + StringVersionComparer.Instance); + + foreach (var rule in rules.OrderByDescending(r => r.Priority)) + { + var range = rule.AffectedRange; + var inRange = true; + + if (range.MinVersion != null) + { + var cmp = comparator.Compare(package.InstalledVersion, range.MinVersion); + inRange &= range.MinInclusive ? cmp >= 0 : cmp > 0; + } + + if (range.MaxVersion != null) + { + var cmp = comparator.Compare(package.InstalledVersion, range.MaxVersion); + inRange &= range.MaxInclusive ? cmp <= 0 : cmp < 0; + } + + if (inRange) + { + return new BackportVerdict( + Status: FixStatus.Vulnerable, + Confidence: VerdictConfidence.Low, // Tier 5 always Low + EvidenceSource: RuleType.Range, + EvidencePointer: new EvidencePointer( + Type: "NvdCpeRange", + Uri: $"nvd:cve/{cve}/cpe/{rule.CpeId}", + SourceDigest: ComputeDigest(rule)), + ConflictReason: null); + } + } + + return new BackportVerdict( + Status: FixStatus.Unknown, + Confidence: VerdictConfidence.Low, + ConflictReason: "No matching range rule"); +} +``` + +--- + +### Phase 2: Derivative Distro Mapping (P1) + +#### Task 2.1: Create DistroDerivative Model and Mappings +- **New File:** src/__Libraries/StellaOps.DistroIntel/DistroDerivative.cs +- **Effort:** 2h +- **Acceptance Criteria:** + - [ ] Define DistroDerivative record with canonical/derivative names, release, confidence + - [ ] Create static DistroMappings class with predefined derivatives + - [ ] Support RHELAlma/Rocky (High confidence), UbuntuMint (Medium), DebianUbuntu (Medium) + - [ ] Add FindDerivativesFor(distro, release) query method + - [ ] Unit test: Query "rhel 9" returns ["almalinux 9", "rocky 9"] + +**Code Snippet:** +```csharp +namespace StellaOps.DistroIntel; + +public enum DerivativeConfidence +{ + High, // Same ABI, byte-for-byte rebuilds (Alma/Rocky from RHEL) + Medium // Derivative with modifications (Ubuntu from Debian, Mint from Ubuntu) +} + +public sealed record DistroDerivative( + string CanonicalDistro, + string DerivativeDistro, + int MajorRelease, + DerivativeConfidence Confidence); + +public static class DistroMappings +{ + public static readonly ImmutableArray Derivatives = + [ + new("rhel", "almalinux", 9, DerivativeConfidence.High), + new("rhel", "rocky", 9, DerivativeConfidence.High), + new("rhel", "centos", 9, DerivativeConfidence.High), + new("rhel", "almalinux", 8, DerivativeConfidence.High), + new("rhel", "rocky", 8, DerivativeConfidence.High), + new("ubuntu", "linuxmint", 22, DerivativeConfidence.Medium), + new("ubuntu", "linuxmint", 20, DerivativeConfidence.Medium), + new("debian", "ubuntu", 12, DerivativeConfidence.Medium), + ]; + + public static IEnumerable FindDerivativesFor( + string distro, + int majorRelease) + { + return Derivatives.Where(d => + d.CanonicalDistro.Equals(distro, StringComparison.OrdinalIgnoreCase) && + d.MajorRelease == majorRelease); + } + + public static decimal GetConfidenceMultiplier(DerivativeConfidence conf) => + conf switch + { + DerivativeConfidence.High => 0.95m, + DerivativeConfidence.Medium => 0.80m, + _ => 0.70m + }; +} +``` + +--- + +#### Task 2.2: Integrate Derivative Mapping into BackportStatusService +- **File:** BackportStatusService.cs +- **Effort:** 2h +- **Acceptance Criteria:** + - [ ] After fetching rules for target distro, if empty, try derivative mappings + - [ ] Query derivative rules and apply confidence penalty + - [ ] Annotate evidence with derivative source + - [ ] Integration test: Scan Rocky 9 with only AlmaLinux OVAL data success + +**Code Snippet:** +```csharp +private async ValueTask> FetchRulesWithDerivativeMapping( + BackportContext context, + PackageInstance package, + CveId cve) +{ + // Try direct distro first + var rules = await _ruleRepository.GetRulesAsync(context, package, cve); + + if (rules.Count == 0) + { + var derivatives = DistroMappings.FindDerivativesFor( + context.Distro, + context.Release); + + foreach (var derivative in derivatives.OrderByDescending(d => d.Confidence)) + { + var derivativeContext = context with + { + Distro = derivative.DerivativeDistro + }; + + var derivativeRules = await _ruleRepository.GetRulesAsync( + derivativeContext, + package, + cve); + + if (derivativeRules.Count > 0) + { + // Apply confidence penalty + var multiplier = DistroMappings.GetConfidenceMultiplier( + derivative.Confidence); + + rules = derivativeRules.Select(r => r with + { + Confidence = r.Confidence * multiplier, + EvidencePointer = r.EvidencePointer with + { + Uri = $"derivative:{derivative.DerivativeDistro}{context.Distro}:{r.EvidencePointer.Uri}" + } + }).ToList(); + + break; // Use first successful derivative + } + } + } + + return rules; +} +``` + +--- + +### Phase 3: Bug ID CVE Mapping (P1) + +#### Task 3.1: Extend ChangelogParser with Bug ID Extraction +- **File:** src/Concelier/__Libraries/StellaOps.Concelier.SourceIntel/ChangelogParser.cs +- **Effort:** 3h +- **Acceptance Criteria:** + - [ ] Add regex patterns for Debian (Closes: #123456), RHBZ (RHBZ#123456), Launchpad (LP: #123456) + - [ ] Extract bug IDs alongside CVE IDs + - [ ] Return ChangelogEntry with both CveIds and BugIds collections + - [ ] Unit test: Parse Debian changelog with "Closes: #987654" bug ID extracted + +**Code Snippet:** +```csharp +[GeneratedRegex(@"Closes:\s*#(\d+)", RegexOptions.IgnoreCase)] +private static partial Regex DebianBugRegex(); + +[GeneratedRegex(@"(?:RHBZ|rhbz)#(\d+)", RegexOptions.IgnoreCase)] +private static partial Regex RhBugzillaRegex(); + +[GeneratedRegex(@"LP:\s*#(\d+)", RegexOptions.IgnoreCase)] +private static partial Regex LaunchpadBugRegex(); + +public sealed record ChangelogEntry( + string Version, + DateTimeOffset Date, + IReadOnlyList CveIds, + IReadOnlyList BugIds, // NEW + string Description); + +public sealed record BugId(string Tracker, string Id) +{ + public override string ToString() => $"{Tracker}#{Id}"; +} + +private static IReadOnlyList ExtractBugIds(string line) +{ + var bugs = new List(); + + foreach (Match m in DebianBugRegex().Matches(line)) + bugs.Add(new BugId("Debian", m.Groups[1].Value)); + + foreach (Match m in RhBugzillaRegex().Matches(line)) + bugs.Add(new BugId("RHBZ", m.Groups[1].Value)); + + foreach (Match m in LaunchpadBugRegex().Matches(line)) + bugs.Add(new BugId("Launchpad", m.Groups[1].Value)); + + return bugs; +} +``` + +--- + +#### Task 3.2: Implement BugCVE Mapping Service +- **New File:** src/__Libraries/StellaOps.BugTracking/IBugCveMappingService.cs +- **Effort:** 4h (including API clients) +- **Acceptance Criteria:** + - [ ] Define IBugCveMappingService.LookupCvesAsync(BugId) + - [ ] Implement Debian Security Tracker API client (https://security-tracker.debian.org/tracker/data/json) + - [ ] Implement Red Hat Bugzilla API stub (cache-based, due to auth complexity) + - [ ] Implement Ubuntu CVE Tracker scraper (https://ubuntu.com/security/cves) + - [ ] Cache results (1 hour TTL) + - [ ] Integration test: Debian bug #987654 CVE-2024-1234 + +**Stub Implementation:** +```csharp +public interface IBugCveMappingService +{ + ValueTask> LookupCvesAsync( + BugId bugId, + CancellationToken cancellationToken = default); +} + +public sealed class DebianSecurityTrackerClient : IBugCveMappingService +{ + private readonly HttpClient _http; + private readonly IMemoryCache _cache; + + public async ValueTask> LookupCvesAsync( + BugId bugId, + CancellationToken ct = default) + { + if (bugId.Tracker != "Debian") + return []; + + var cacheKey = $"debian:bug:{bugId.Id}"; + if (_cache.TryGetValue(cacheKey, out IReadOnlyList? cached)) + return cached!; + + var json = await _http.GetStringAsync( + "https://security-tracker.debian.org/tracker/data/json", + ct); + + // Parse JSON, extract CVEs for bug ID + var cves = ParseDebianTrackerJson(json, bugId.Id); + + _cache.Set(cacheKey, cves, TimeSpan.FromHours(1)); + return cves; + } +} +``` + +--- + +### Phase 4: Function Extraction from Hunks (P2) + +#### Task 4.1: Add Function Signature Extraction to HunkSigExtractor +- **File:** src/Feedser/StellaOps.Feedser.Core/HunkSigExtractor.cs +- **Effort:** 4h +- **Acceptance Criteria:** + - [ ] Extract C/C++ functions (static void foo(, int main() + - [ ] Extract Python functions (def foo(, class Foo:) + - [ ] Extract Go functions ( unc (r *Receiver) Method() + - [ ] Populate PatchHunkSig.AffectedFunctions + - [ ] Unit test: C patch with static int ssl_verify(SSL *ssl) function extracted + +**Code Snippet:** +```csharp +[GeneratedRegex(@"^\s*(?:static\s+|inline\s+)?(?:\w+\s+)+(\w+)\s*\(", RegexOptions.Multiline)] +private static partial Regex CFunctionRegex(); + +[GeneratedRegex(@"^\s*def\s+(\w+)\s*\(", RegexOptions.Multiline)] +private static partial Regex PythonFunctionRegex(); + +[GeneratedRegex(@"^\s*func\s+(?:\(\w+\s+\*?\w+\)\s+)?(\w+)\s*\(", RegexOptions.Multiline)] +private static partial Regex GoFunctionRegex(); + +private static IReadOnlyList ExtractFunctionsFromContext(PatchHunk hunk) +{ + var functions = new HashSet(); + var context = hunk.Context; + + foreach (Match m in CFunctionRegex().Matches(context)) + functions.Add(m.Groups[1].Value); + + foreach (Match m in PythonFunctionRegex().Matches(context)) + functions.Add(m.Groups[1].Value); + + foreach (Match m in GoFunctionRegex().Matches(context)) + functions.Add(m.Groups[1].Value); + + return functions.ToArray(); +} + +// Update ExtractHunkSigs: +AffectedFunctions = ExtractFunctionsFromContext(hunk), +``` + +--- + +### Phase 5: Confidence Tier Realignment (P2) + +#### Task 5.1: Update RulePriority Enum +- **File:** src/Concelier/__Libraries/StellaOps.Concelier.BackportProof/Models/FixRuleModels.cs +- **Effort:** 1h +- **Acceptance Criteria:** + - [ ] Rename/add priority values to match 5-tier hierarchy + - [ ] Ensure tier ordering: Tier 1 > Tier 2 > ... > Tier 5 + - [ ] Update existing rule creation code to use new priorities + - [ ] Unit test: Verify priority ordering in resolver + +**Code Snippet:** +```csharp +public enum RulePriority +{ + // Tier 1: Derivative OVAL/CSAF + DistroNativeOval = 100, + DerivativeOvalHigh = 95, // Alma/Rocky for RHEL + DerivativeOvalMedium = 90, // Mint for Ubuntu + + // Tier 2: Changelog markers + ChangelogExplicitCve = 85, + ChangelogBugIdMapped = 75, + + // Tier 3: Source patch files + SourcePatchExactMatch = 70, + SourcePatchFuzzyMatch = 60, + + // Tier 4: Upstream commit mapping + UpstreamCommitExactParity = 55, + UpstreamCommitPartialMatch = 45, + + // Tier 5: NVD range fallback + NvdRangeHeuristic = 20 +} +``` + +--- + +#### Task 5.2: Map Priorities to Confidence Levels +- **File:** BackportStatusService.cs +- **Effort:** 1h +- **Acceptance Criteria:** + - [ ] Add GetConfidenceForPriority(RulePriority) helper + - [ ] Return VerdictConfidence.High for Tier 1-2, Medium for Tier 3-4, Low for Tier 5 + - [ ] Use in all verdict creation paths + - [ ] Unit test: Priority 100 High, Priority 20 Low + +**Code Snippet:** +```csharp +private static VerdictConfidence GetConfidenceForPriority(RulePriority priority) => + priority switch + { + >= RulePriority.ChangelogBugIdMapped => VerdictConfidence.High, + >= RulePriority.UpstreamCommitPartialMatch => VerdictConfidence.Medium, + _ => VerdictConfidence.Low + }; +``` + +--- + +## Testing Strategy + +### Unit Tests (Per Task) +- Task 1.1: BackportStatusServiceTests.cs::VersionComparatorIntegration +- Task 1.2: BackportStatusServiceTests.cs::RangeRuleEvaluation +- Task 2.1: DistroMappingsTests.cs +- Task 2.2: BackportStatusServiceTests.cs::DerivativeDistroMapping +- Task 3.1: ChangelogParserTests.cs::BugIdExtraction +- Task 3.2: BugCveMappingServiceTests.cs +- Task 4.1: HunkSigExtractorTests.cs::FunctionExtraction +- Task 5.1/5.2: FixRuleModelsTests.cs::ConfidenceMapping + +### Integration Tests (Golden Cases) + +#### Test Case 1: CVE-2024-26130 (OpenSSL on Rocky 9) +```yaml +Scenario: Backported fix with derivative OVAL +Given: + - CVE: CVE-2024-26130 + - Package: openssl-3.0.7-24.el9 + - Distro: rocky 9 + - OVAL exists for: almalinux 9 (not rocky 9) +Expected: + - Status: FIXED + - Confidence: High (0.95) + - Evidence: AlmaLinux OVAL ALSA-2024-1234, mapped to Rocky + - Tier: 1 (Derivative OVAL) +``` + +#### Test Case 2: CVE-2023-12345 (curl on Debian with bug ID) +```yaml +Scenario: Changelog with Debian bug ID +Given: + - CVE: CVE-2023-12345 + - Package: curl-7.88.1-10+deb12u1 + - Distro: debian 12 + - Changelog: "Closes: #987654" (maps to CVE-2023-12345) +Expected: + - Status: FIXED + - Confidence: Medium (0.75) + - Evidence: Debian changelog, bug #987654 CVE-2023-12345 + - Tier: 2 (Changelog bug ID) +``` + +#### Test Case 3: CVE-2024-99999 (zlib with NVD range only) +```yaml +Scenario: Fallback to NVD range +Given: + - CVE: CVE-2024-99999 + - Package: zlib-1.2.11-r3 + - Distro: alpine 3.18 + - No OVAL, no changelog, no patches + - NVD range: [1.2.0, 1.2.12) vulnerable +Expected: + - Status: VULNERABLE + - Confidence: Low (0.40) + - Evidence: NVD CPE range heuristic + - Tier: 5 (NVD fallback) +``` + +### Performance Tests +- Measure resolver latency: target <50ms for Tier 1-3, <500ms for Tier 4 (upstream git) +- Bulk scan: 10,000 CVEpackage combinations should complete within 5 minutes +- Cache hit rate for bugCVE mapping: target >80% + +--- + +## Rollout Plan + +### Phase 1 (Week 1): P0 Tasks +- Days 1-2: Task 1.1 (Version comparators) + Task 1.2 (Range rules) +- Day 3: Unit tests + integration testing +- Day 4: Deploy to staging, validate with golden cases +- Day 5: Production canary (10% traffic) + +### Phase 2 (Week 2): P1 Tasks +- Days 1-2: Task 2.1 + 2.2 (Derivative mapping) +- Days 3-4: Task 3.1 + 3.2 (Bug ID mapping) +- Day 5: Full production rollout + +### Phase 3 (Week 3): P2 Polish +- Days 1-2: Task 4.1 (Function extraction) +- Day 3: Task 5.1 + 5.2 (Confidence realignment) +- Days 4-5: Documentation + observability dashboards + +--- + +## Success Metrics + +| Metric | Baseline (Current) | Target (Post-Sprint) | Measurement | +|--------|-------------------|----------------------|-------------| +| False positive rate | 35% | <5% | Manual audit of 500 random verdicts | +| False negative rate | 12% | <3% | Regression test suite (50 known vulns) | +| Tier 1 evidence usage | 0% | >40% | % verdicts using derivative OVAL | +| Tier 5 fallback rate | 100% | <20% | % verdicts from NVD ranges only | +| Average confidence score | 0.50 (Medium) | >0.75 (Medium-High) | Weighted average of verdicts | +| Time to verdict | 150ms | <100ms | P95 latency for single CVE evaluation | + +--- + +## Risk Mitigation + +| Risk | Likelihood | Impact | Mitigation | +|------|-----------|--------|------------| +| Version comparer regressions | Medium | High | Extensive unit tests, gradual rollout with canary | +| Derivative OVAL mismatch (NEVRA drift) | Low | Medium | Require exact NEVRA match, log mismatches | +| Bug tracker APIs rate-limit/fail | High | Medium | Aggressive caching (1h TTL), fallback to direct CVE only | +| Function extraction false positives | Medium | Low | Fuzzy matching with threshold, manual review for P2 | +| Confidence inflation | Low | High | Audit trail of all evidence, periodic manual validation | + +--- + +## Appendix + +### A. File Modification Checklist +- [ ] BackportStatusService.cs (Tasks 1.1, 1.2, 2.2, 5.2) +- [ ] FixRuleModels.cs (Task 5.1) +- [ ] ChangelogParser.cs (Task 3.1) +- [ ] HunkSigExtractor.cs (Task 4.1) +- [ ] New: DistroDerivative.cs (Task 2.1) +- [ ] New: IBugCveMappingService.cs + implementations (Task 3.2) + +### B. Dependency Updates +```xml + + + + + +``` + +### C. Configuration Changes +```json +{ + "BackportResolver": { + "EnableDerivativeMapping": true, + "DerivativeConfidencePenalty": 0.05, + "BugTrackerCache": { + "TtlHours": 1, + "MaxEntries": 10000 + }, + "TierTimeouts": { + "Tier1Ms": 500, + "Tier2Ms": 200, + "Tier3Ms": 300, + "Tier4Ms": 2000, + "Tier5Ms": 100 + } + } +} +``` + +--- + +**End of Sprint Document**