Skip to content

Commit 3121bc0

Browse files
committed
rhcc: handle known_not_affected without version comparison
For VEX "known_not_affected" assertions (vuln.Invert == true), the package name match from the database query is sufficient. Version comparison is skipped because: 1. The VEX assertion covers the entire container, not a specific version 2. The FixedInVersion field is empty for these assertions, which would cause rpmver.Parse to fail Also adds a version Range to OCI PURL entries in the VEX parser to ensure proper version matching semantics in the database query. Signed-off-by: J. Victor Martins <jvdm@sdf.org>
1 parent 5963cc8 commit 3121bc0

4 files changed

Lines changed: 119 additions & 10 deletions

File tree

rhel/rhcc/matcher.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ func (*matcher) Name() string { return "rhel-container-matcher" }
2727

2828
// Filter implements [driver.Matcher].
2929
func (*matcher) Filter(r *claircore.IndexRecord) bool {
30-
return r.Repository != nil &&
31-
r.Repository.Key == RepositoryKey
30+
return r.Repository != nil && r.Repository.Key == RepositoryKey
3231
}
3332

3433
// Query implements [driver.Matcher].
@@ -52,6 +51,14 @@ func (*matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, v
5251
}
5352

5453
slog.DebugContext(ctx, "comparing versions", "record", record.Package.Version, "vulnerability", vuln.FixedInVersion)
54+
55+
// For known_not_affected assertions (Invert == true), the package name match
56+
// from the DB query is sufficient. No version comparison is needed because the
57+
// VEX assertion covers the entire container, not a specific version.
58+
if vuln.Invert {
59+
return true, nil
60+
}
61+
5562
pkgVer, err := rpmver.Parse(record.Package.Version)
5663
if err != nil {
5764
return false, fmt.Errorf("rhcc: unable to parse version %q: %w", record.Package.Version, err)

rhel/rhcc/matcher_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package rhcc
2+
3+
import (
4+
"testing"
5+
6+
"github.com/quay/claircore"
7+
"github.com/quay/claircore/test"
8+
)
9+
10+
func TestMatcherVulnerable(t *testing.T) {
11+
t.Parallel()
12+
ctx := test.Logging(t)
13+
m := &matcher{}
14+
15+
t.Run("Inverted", func(t *testing.T) {
16+
record := &claircore.IndexRecord{
17+
Package: &claircore.Package{Name: "mta/mta-rhel8-operator", Version: "7.0.3-13"},
18+
Repository: &GoldRepo,
19+
}
20+
vuln := &claircore.Vulnerability{
21+
Name: "CVE-2024-24786",
22+
Invert: true,
23+
Repo: &GoldRepo,
24+
}
25+
got, err := m.Vulnerable(ctx, record, vuln)
26+
if err != nil {
27+
t.Fatal(err)
28+
}
29+
if !got {
30+
t.Error("expected vulnerable=true for inverted vulnerability")
31+
}
32+
})
33+
34+
t.Run("Normal", func(t *testing.T) {
35+
record := &claircore.IndexRecord{
36+
Package: &claircore.Package{Name: "quay/quay-rhel8", Version: "v3.5.5-4"},
37+
Repository: &GoldRepo,
38+
}
39+
vuln := &claircore.Vulnerability{
40+
Name: "CVE-2023-12345",
41+
FixedInVersion: "v3.5.6-1",
42+
Repo: &GoldRepo,
43+
}
44+
got, err := m.Vulnerable(ctx, record, vuln)
45+
if err != nil {
46+
t.Fatal(err)
47+
}
48+
if !got {
49+
t.Error("expected vulnerable=true when version < fixed")
50+
}
51+
})
52+
53+
t.Run("Fixed", func(t *testing.T) {
54+
record := &claircore.IndexRecord{
55+
Package: &claircore.Package{Name: "quay/quay-rhel8", Version: "v3.5.7-1"},
56+
Repository: &GoldRepo,
57+
}
58+
vuln := &claircore.Vulnerability{
59+
Name: "CVE-2023-12345",
60+
FixedInVersion: "v3.5.6-1",
61+
Repo: &GoldRepo,
62+
}
63+
got, err := m.Vulnerable(ctx, record, vuln)
64+
if err != nil {
65+
t.Fatal(err)
66+
}
67+
if got {
68+
t.Error("expected vulnerable=false when version >= fixed")
69+
}
70+
})
71+
}

rhel/vex/parser.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,16 @@ func (c *creator) knownNotAffectedVulnerabilities(ctx context.Context, v *csaf.V
980980
case packageurl.TypeOCI:
981981
vuln.Repo = c.rc.Get(st.WFN, rhcc.RepositoryKey)
982982
vuln.Package.Kind = types.AncestryPackage
983+
// Use a flood-gates range that matches all versions. For
984+
// known_not_affected assertions, the package name match is
985+
// sufficient; the matcher skips version comparison when
986+
// Invert is true.
987+
vuln.Range = &claircore.Range{
988+
Lower: new(rhctag.Version).Version(true),
989+
Upper: (&rhctag.Version{
990+
Major: math.MaxInt32,
991+
}).Version(true),
992+
}
983993
default:
984994
panic("unreachable")
985995
}

rhel/vex/parser_test.go

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -357,10 +357,11 @@ func TestParse(t *testing.T) {
357357
}
358358

359359
testcases := []struct {
360-
name string
361-
filenames []string
362-
expectedVulns int
363-
expectedDeleted int
360+
name string
361+
filenames []string
362+
expectedVulns int
363+
expectedDeleted int
364+
expectedAncestry int
364365
}{
365366
{
366367
name: "five_advisories_four_deletions",
@@ -383,10 +384,11 @@ func TestParse(t *testing.T) {
383384
expectedDeleted: 0,
384385
},
385386
{
386-
name: "cve-2024-24786",
387-
filenames: []string{"testdata/cve-2024-24786.json"},
388-
expectedVulns: 1828,
389-
expectedDeleted: 0,
387+
name: "cve-2024-24786",
388+
filenames: []string{"testdata/cve-2024-24786.json"},
389+
expectedVulns: 1828,
390+
expectedDeleted: 0,
391+
expectedAncestry: 732,
390392
},
391393
{
392394
name: "cve-2022-38752",
@@ -462,6 +464,24 @@ func TestParse(t *testing.T) {
462464
if len(deleted) != tc.expectedDeleted {
463465
t.Fatalf("expected %d deleted but got %d", tc.expectedDeleted, len(deleted))
464466
}
467+
if tc.expectedAncestry > 0 {
468+
var ancestryCount int
469+
for _, v := range vulns {
470+
if v.Package.Kind != types.AncestryPackage {
471+
continue
472+
}
473+
ancestryCount++
474+
if !v.Invert {
475+
t.Errorf("ancestry vuln %q: expected Invert=true", v.Name)
476+
}
477+
if v.Range == nil {
478+
t.Errorf("ancestry vuln %q: expected Range to be set", v.Name)
479+
}
480+
}
481+
if ancestryCount != tc.expectedAncestry {
482+
t.Errorf("expected %d ancestry vulns but got %d", tc.expectedAncestry, ancestryCount)
483+
}
484+
}
465485
})
466486
}
467487
}
@@ -666,3 +686,4 @@ func TestExtractPackageName(t *testing.T) {
666686
})
667687
}
668688
}
689+

0 commit comments

Comments
 (0)