@@ -35,6 +35,9 @@ public class ScanCommand implements Callable<Integer> {
35
35
@ Option (names = {"-d" , "--database" })
36
36
Path databaseFilePath ;
37
37
38
+ @ Option (names = {"-i" , "--ensure-indexes" })
39
+ boolean ensureIndexes ;
40
+
38
41
@ Parameters
39
42
Path bomFilePath ;
40
43
@@ -44,15 +47,31 @@ public Integer call() throws Exception {
44
47
.create ("jdbc:sqlite:%s" .formatted (databaseFilePath ))
45
48
.installPlugin (new SQLitePlugin ());
46
49
50
+ if (ensureIndexes ) {
51
+ jdbi .useHandle (handle -> {
52
+ handle .execute ("""
53
+ create index if not exists matching_criteria_purl_ns_idx
54
+ on matching_criteria(purl_type, purl_namespace, purl_name)
55
+ where purl_namespace is not null;
56
+ """ );
57
+
58
+ handle .execute ("""
59
+ create index if not exists matching_criteria_purl_idx
60
+ on matching_criteria(purl_type, purl_name)
61
+ where purl_namespace is null;
62
+ """ );
63
+ });
64
+ }
65
+
47
66
final byte [] bomBytes = Files .readAllBytes (bomFilePath );
48
67
final Bom bom = BomParserFactory .createParser (bomBytes ).parse (bomBytes );
49
68
50
69
try (final Handle handle = jdbi .open ()) {
51
70
// TODO: Consider metadata.component, nested components etc.
52
71
53
72
for (final Component component : bom .getComponents ()) {
54
- final Set <String > vulnIds = scan (handle , component );
55
- if (!vulnIds .isEmpty ()) {
73
+ final Set <MatchMetadata > matches = scan (handle , component );
74
+ if (!matches .isEmpty ()) {
56
75
String componentName = component .getName ();
57
76
if (component .getGroup () != null ) {
58
77
componentName = component .getGroup () + "/" + componentName ;
@@ -62,7 +81,14 @@ public Integer call() throws Exception {
62
81
}
63
82
64
83
// TODO: Move reporting to the very end.
65
- System .out .println (componentName + ": " + vulnIds .stream ().sorted ().toList ());
84
+ System .out .println (componentName + ":" );
85
+ for (final MatchMetadata match : matches ) {
86
+ System .out .println ("- %s\n Matched range: %s\n Source: %s" .formatted (
87
+ match .vulnId (),
88
+ match .criteriaVers (),
89
+ match .criteriaSource ()));
90
+ }
91
+ System .out .println ();
66
92
}
67
93
}
68
94
}
@@ -74,10 +100,16 @@ public Integer call() throws Exception {
74
100
return 0 ;
75
101
}
76
102
77
- private Set <String > scan (
103
+ private record MatchMetadata (
104
+ String vulnId ,
105
+ String criteriaSource ,
106
+ String criteriaVers ) {
107
+ }
108
+
109
+ private Set <MatchMetadata > scan (
78
110
final Handle handle ,
79
111
final Component component ) throws MalformedPackageURLException , CpeParsingException {
80
- final var affectedVulnIds = new HashSet <String >();
112
+ final var affectedVulnIds = new HashSet <MatchMetadata >();
81
113
82
114
if (component .getCpe () != null ) {
83
115
final var cpe = CpeParser .parse (component .getCpe ());
@@ -99,9 +131,31 @@ private Set<String> scan(
99
131
100
132
final Vers vers = Vers .parse (criteriaRecord .versions ());
101
133
if (vers .contains (purl .getVersion ())) {
102
- // TODO: Check additional criteria.
134
+ // Handle cases where a Debian vulnerability only apply to specific
135
+ // releases of Debian. Note that this requires both the criteria, as well
136
+ // as the package to declare a Debian release version.
137
+ // It can't be reliably deduced from package versions or vers ranges alone.
138
+ // TODO: For the love of god make this less atrocious.
139
+ if (purl .getType ().equals (PackageURL .StandardTypes .DEBIAN )
140
+ && purl .getQualifiers () != null
141
+ && purl .getQualifiers ().containsKey ("distro" )
142
+ && purl .getQualifiers ().get ("distro" ).startsWith ("debian-" )
143
+ && "debian-version" .equals (criteriaRecord .additionalCriteriaType ())) {
144
+ final String distroVersion = purl .getQualifiers ().get ("distro" ).replaceFirst ("^debian-" , "" );
145
+ if (!distroVersion .startsWith (new String (criteriaRecord .additionalCriteria ()))) {
146
+ System .out .println (
147
+ "Discarding range %s due to Debian version mismatch (package=%s, criteria=%s)" .formatted (
148
+ criteriaRecord .versions (), distroVersion , new String (criteriaRecord .additionalCriteria ())));
149
+ continue ;
150
+ }
151
+ }
152
+
153
+ // TODO: Check more additional criteria types.
103
154
104
- affectedVulnIds .add (criteriaRecord .vulnId ());
155
+ affectedVulnIds .add (new MatchMetadata (
156
+ criteriaRecord .vulnId (),
157
+ criteriaRecord .sourceName (),
158
+ criteriaRecord .versions ()));
105
159
}
106
160
}
107
161
}
0 commit comments