Skip to content

Commit 337f050

Browse files
committed
Handle Debian release versions
Signed-off-by: nscuro <[email protected]>
1 parent ff579db commit 337f050

File tree

4 files changed

+71
-8
lines changed

4 files changed

+71
-8
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ docker run -it --rm \
8282
-v "$(pwd):/workspace" \
8383
-w '/workspace' \
8484
ghcr.io/dependencytrack/vuln-db:snapshot \
85-
scan --database=all.sqlite bom.json
85+
scan --ensure-indexes --database=all.sqlite bom.json
8686
```
8787

8888
## Data model

src/main/java/org/dependencytrack/vulndb/cli/ScanCommand.java

+61-7
Original file line numberDiff line numberDiff line change
@@ -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
}

src/main/java/org/dependencytrack/vulndb/source/osv/OsvImporter.java

+8
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,14 @@ private void processAdvisory(final OsvAdvisory advisory) {
177177
} catch (IOException e) {
178178
LOGGER.warn("Failed to serialize go-imports {}", imports, e);
179179
}
180+
} else if (affected.pkg().ecosystem().toLowerCase().startsWith("debian")) {
181+
final String[] ecosystemParts = affected.pkg().ecosystem().split(":", 2);
182+
if (ecosystemParts.length == 2) {
183+
// TODO: Should be a JSON object to make it less ambiguous.
184+
// TODO: Can this be generalized? Do we need this for RedHat etc. too?
185+
additionalCriteria = ecosystemParts[1].trim().getBytes();
186+
additionalCriteriaType = "debian-version";
187+
}
180188
}
181189

182190
if (affected.ranges() != null) {

src/main/resources/cleanup.sql

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ drop table if exists matching_criteria;
22
drop table if exists vuln_reference;
33
drop table if exists vuln_rating;
44
drop table if exists vuln_alias;
5+
drop table if exists vuln_related;
56
drop table if exists vuln_data;
67
drop table if exists vuln;
78
drop table if exists source_metadata;

0 commit comments

Comments
 (0)