Skip to content

Commit 2779b70

Browse files
committed
rpm: add common version comparison function
Signed-off-by: Hank Donnay <hdonnay@redhat.com> Change-Id: I5f1f740f42b9ef6a8184a73873dbed56aebb5f6d JJ: See-Also: CLAIRDEV-NNNN JJ: Closes: #NNNN
1 parent 8155fa3 commit 2779b70

2 files changed

Lines changed: 127 additions & 0 deletions

File tree

internal/rpm/matcher.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package rpm
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/quay/claircore"
8+
"github.com/quay/claircore/internal/rpmver"
9+
)
10+
11+
// MatchVulnerable is a function implementing "driver.Matcher.Vulnerable"
12+
// in a common way.
13+
//
14+
// Given a package version "P" and vulnerability "V":
15+
//
16+
// - If a fixed version "F" is specified in "V", "P < F" is reported.
17+
// - If a package version "F" is specified in "V", "P <= F" is reported.
18+
// - If no version is provided in "V", this function compares against an
19+
// "infinite" version.
20+
//
21+
// In addition to this version comparison, the architectures are compared.
22+
func MatchVulnerable(ctx context.Context, rec *claircore.IndexRecord, vuln *claircore.Vulnerability) (bool, error) {
23+
p, err := rpmver.Parse(rec.Package.Version)
24+
if err != nil {
25+
return false, fmt.Errorf("rpm: unable to parse package version: %w", err)
26+
}
27+
28+
var v rpmver.Version
29+
cmp := isLTE
30+
switch {
31+
case vuln.FixedInVersion != "":
32+
v, err = rpmver.Parse(vuln.FixedInVersion)
33+
cmp = isLT
34+
case vuln.Package.Version != "":
35+
v, err = rpmver.Parse(vuln.Package.Version)
36+
default:
37+
v, err = rpmver.Parse("65535:65535-65535")
38+
}
39+
if err != nil {
40+
return false, fmt.Errorf("rpm: unable to parse vulnerability version: %w", err)
41+
}
42+
43+
return cmp(rpmver.Compare(&p, &v)) && vuln.ArchOperation.Cmp(rec.Package.Arch, vuln.Package.Arch), nil
44+
}
45+
46+
func isLTE(cmp int) bool { return cmp != 1 }
47+
func isLT(cmp int) bool { return cmp == -1 }

internal/rpm/matcher_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package rpm
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/quay/claircore"
8+
"github.com/quay/claircore/internal/rpmver"
9+
)
10+
11+
func TestMatchVulnerable(t *testing.T) {
12+
record := func(v string) *claircore.IndexRecord {
13+
return &claircore.IndexRecord{Package: &claircore.Package{Version: v, Arch: "x86_64"}}
14+
}
15+
tcs := []struct {
16+
Name string
17+
Record *claircore.IndexRecord
18+
Vulnerability *claircore.Vulnerability
19+
Want bool
20+
Err error
21+
}{
22+
{
23+
Name: "Infinite",
24+
Record: record("0:1.2.3-4"),
25+
Vulnerability: &claircore.Vulnerability{Package: new(claircore.Package)},
26+
Want: true,
27+
},
28+
{
29+
Name: "InfiniteWrongArch",
30+
Record: record("0:1.2.3-4"),
31+
Vulnerability: &claircore.Vulnerability{
32+
Package: &claircore.Package{Arch: "aarch64"},
33+
ArchOperation: claircore.OpEquals,
34+
},
35+
Want: false,
36+
},
37+
{
38+
Name: "BadRecordVersion",
39+
Record: record("1.2.3"), // Not an EVR
40+
Err: rpmver.ErrParse,
41+
},
42+
{
43+
Name: "BadVulnerabilityVersion",
44+
Record: record("1.2.3-4"),
45+
Vulnerability: &claircore.Vulnerability{
46+
FixedInVersion: "2.0",
47+
},
48+
Err: rpmver.ErrParse,
49+
},
50+
{
51+
Name: "FixedIn",
52+
Record: record("1.2.3-4"),
53+
Vulnerability: &claircore.Vulnerability{
54+
FixedInVersion: "1.2.3-4",
55+
},
56+
Want: false,
57+
},
58+
{
59+
Name: "Package",
60+
Record: record("1.2.3-4"),
61+
Vulnerability: &claircore.Vulnerability{
62+
Package: &claircore.Package{Version: "1.2.3-4"},
63+
},
64+
Want: true,
65+
},
66+
}
67+
68+
for _, tc := range tcs {
69+
t.Run(tc.Name, func(t *testing.T) {
70+
ctx := t.Context()
71+
got, err := MatchVulnerable(ctx, tc.Record, tc.Vulnerability)
72+
if !errors.Is(err, tc.Err) {
73+
t.Errorf("unexpected error: %v", err)
74+
}
75+
if got != tc.Want {
76+
t.Errorf("got: %v, want: %v", got, tc.Want)
77+
}
78+
})
79+
}
80+
}

0 commit comments

Comments
 (0)