@@ -35,6 +35,9 @@ public class ScanCommand implements Callable<Integer> {
3535 @ Option (names = {"-d" , "--database" })
3636 Path databaseFilePath ;
3737
38+ @ Option (names = {"-i" , "--ensure-indexes" })
39+ boolean ensureIndexes ;
40+
3841 @ Parameters
3942 Path bomFilePath ;
4043
@@ -44,15 +47,31 @@ public Integer call() throws Exception {
4447 .create ("jdbc:sqlite:%s" .formatted (databaseFilePath ))
4548 .installPlugin (new SQLitePlugin ());
4649
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+
4766 final byte [] bomBytes = Files .readAllBytes (bomFilePath );
4867 final Bom bom = BomParserFactory .createParser (bomBytes ).parse (bomBytes );
4968
5069 try (final Handle handle = jdbi .open ()) {
5170 // TODO: Consider metadata.component, nested components etc.
5271
5372 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 ()) {
5675 String componentName = component .getName ();
5776 if (component .getGroup () != null ) {
5877 componentName = component .getGroup () + "/" + componentName ;
@@ -62,7 +81,14 @@ public Integer call() throws Exception {
6281 }
6382
6483 // 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 ();
6692 }
6793 }
6894 }
@@ -74,10 +100,16 @@ public Integer call() throws Exception {
74100 return 0 ;
75101 }
76102
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 (
78110 final Handle handle ,
79111 final Component component ) throws MalformedPackageURLException , CpeParsingException {
80- final var affectedVulnIds = new HashSet <String >();
112+ final var affectedVulnIds = new HashSet <MatchMetadata >();
81113
82114 if (component .getCpe () != null ) {
83115 final var cpe = CpeParser .parse (component .getCpe ());
@@ -99,9 +131,31 @@ private Set<String> scan(
99131
100132 final Vers vers = Vers .parse (criteriaRecord .versions ());
101133 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.
103154
104- affectedVulnIds .add (criteriaRecord .vulnId ());
155+ affectedVulnIds .add (new MatchMetadata (
156+ criteriaRecord .vulnId (),
157+ criteriaRecord .sourceName (),
158+ criteriaRecord .versions ()));
105159 }
106160 }
107161 }
0 commit comments