Skip to content

Commit

Permalink
Add YoctoScannerParser
Browse files Browse the repository at this point in the history
Yocto project support CVE security vulnerabilities using cve-check in the specific
image or target you are building, add the following setting to your configuration:

INHERIT += "cve-check"

status of each CVE: Patched, Unpatched or Ignored

The scanner look only for Unpatched package and calculate the severity using
the score_v2 or score_v3.

The generated from Yocto can be collected in build/tmp/log/cve/cve-summury.json

Signed-off-by: Michael Trimarchi <[email protected]>
  • Loading branch information
panicking committed Aug 31, 2024
1 parent ce66a5e commit 04c2d27
Show file tree
Hide file tree
Showing 5 changed files with 622 additions and 0 deletions.
108 changes: 108 additions & 0 deletions src/main/java/edu/hm/hafner/analysis/parser/YoctoScannerParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package edu.hm.hafner.analysis.parser;

import org.json.JSONArray;
import org.json.JSONObject;

import edu.hm.hafner.analysis.Issue;
import edu.hm.hafner.analysis.IssueBuilder;
import edu.hm.hafner.analysis.Report;
import edu.hm.hafner.analysis.Severity;

import static j2html.TagCreator.*;

/**
* Parser for Yocto Scanner CLI (scannercli) tool.
*
* @author Juri Duval
*/
public class YoctoScannerParser extends JsonIssueParser {
private static final String VALUE_NOT_SET = "-";
private static final long serialVersionUID = 1L;
private static final Double INVALID_SCORE = -1.0;

@Override
protected void parseJsonObject(final Report report, final JSONObject jsonReport, final IssueBuilder issueBuilder) {
JSONArray packages = jsonReport.optJSONArray("package");
if (packages != null) {
parseResources(report, packages, issueBuilder);
}
}

private void parseResources(final Report report, final JSONArray packages, final IssueBuilder issueBuilder) {
for (int i = 0; i < packages.length(); i++) {
final Object item = packages.get(i);
if (item instanceof JSONObject) {
final JSONObject resourceWrapper = (JSONObject) item;
if (!resourceWrapper.isNull("issue")) {
parseVulnerabilities(report, issueBuilder, resourceWrapper);
}
}
}
}

private void parseVulnerabilities(final Report report, final IssueBuilder issueBuilder,
final JSONObject resourceWrapper) {
final JSONArray vulnerabilities = resourceWrapper.getJSONArray("issue");
for (Object vulnerability : vulnerabilities) {
if (vulnerability instanceof JSONObject) {
final JSONObject obj = (JSONObject) vulnerability;
final String status = obj.getString("status");
boolean unpatched = "Unpatched".equals(status);
if (unpatched) {
report.add(convertToIssue(resourceWrapper, obj, issueBuilder));
}
}
}
}

private Issue convertToIssue(final JSONObject resource, final JSONObject vulnerability,
final IssueBuilder issueBuilder) {
final String packageName = resource.getString("name");
return issueBuilder
.setPackageName(packageName)
.setSeverity(mapSeverity(vulnerability))
.setMessage(vulnerability.optString("id", "UNKNOWN"))
.setDescription(formatDescription(packageName, resource, vulnerability))
.buildAndClean();
}

private Severity mapSeverity(final JSONObject vulnerability) {
Double score = INVALID_SCORE;
boolean hasScoreV3 = vulnerability.has("scorev3");

if (hasScoreV3) {
score = vulnerability.getDouble("scorev3");
}

if (score <= 0) {
score = vulnerability.getDouble("scorev2");
}

if (score >= 0 && score < 4.0) {
return Severity.WARNING_LOW;
}
else if (score >= 4.0 && score < 7.0) {
return Severity.WARNING_NORMAL;
}
else if (score >= 7.0 && score <= 10.0) {
return Severity.WARNING_HIGH;
}

return Severity.ERROR;
}

private String formatDescription(final String packageName, final JSONObject resource, final JSONObject vulnerability) {
final String version = resource.optString("version", VALUE_NOT_SET);
final String layer = resource.optString("layer", "UNKOWN");
final String vector = vulnerability.optString("vector", "UNKOWN");
final String link = vulnerability.optString("link", "UNKOWN");
final String description = vulnerability.optString("summary", "");

return join(div(b("Package: "), text(packageName)),
div(b("Version: "), text(version)),
div(b("Link: "), text(link)),
div(b("Yocto Layer: "), text(layer)),
div(b("Vector: "), text(vector)),
p(text(description))).render();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ public class ParserRegistry {
new VeraCodePipelineScannerDescriptor(),
new XlcDescriptor(),
new YamlLintDescriptor(),
new YoctoScannerDescriptor(),
new XmlLintDescriptor(),
new YuiCompressorDescriptor(),
new ZptLintDescriptor()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package edu.hm.hafner.analysis.registry;

import edu.hm.hafner.analysis.IssueParser;
import edu.hm.hafner.analysis.parser.YoctoScannerParser;

import static j2html.TagCreator.*;

/**
* A descriptor for Yocto Scanner.
*
* @author Juri Duval
*/
class YoctoScannerDescriptor extends ParserDescriptor {
private static final String ID = "Yocto cli";
private static final String NAME = "Yocto Scanner";

YoctoScannerDescriptor() {
super(ID, NAME);
}

@Override
public IssueParser createParser(final Option... options) {
return new YoctoScannerParser();
}

@Override
public String getHelp() {
return join(text("Use commandline"),
code("bitbake <your product image>"),
text(", add INHERIT += \"cve-check\" in your local.conf"),
a("Yocto Scanner").withHref("https://docs.yoctoproject.org/dev/dev-manual/vulnerabilities.html"),
text("for usage details.")).render();
}

@Override
public String getUrl() {
return "https://docs.yoctoproject.org/dev/dev-manual/vulnerabilities.html";
}

@Override
public String getIconUrl() {
return "https://www.yoctoproject.org/wp-content/uploads/sites/32/2023/09/YoctoProject_Logo_RGB_White_small.svg";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package edu.hm.hafner.analysis.parser;

import org.junit.jupiter.api.Test;

import edu.hm.hafner.analysis.IssueParser;
import edu.hm.hafner.analysis.ParsingException;
import edu.hm.hafner.analysis.Report;
import edu.hm.hafner.analysis.Severity;
import edu.hm.hafner.analysis.assertions.SoftAssertions;
import edu.hm.hafner.analysis.registry.AbstractParserTest;

import static edu.hm.hafner.analysis.assertions.Assertions.*;

/**
* Tests the class {@link YoctoScannerParser}.
*/
class YoctoScannerParserTest extends AbstractParserTest {

YoctoScannerParserTest() {
super("yocto_scanner_result.json");
}

@Override
protected void assertThatIssuesArePresent(final Report report, final SoftAssertions softly) {
softly.assertThat(report).hasSize(25);

softly.assertThat(report.get(0))
.hasSeverity(Severity.WARNING_LOW)
.hasPackageName("acl")
.hasMessage("CVE-2009-4411");
softly.assertThat(report.get(3))
.hasSeverity(Severity.WARNING_NORMAL)
.hasPackageName("automake-native")
.hasMessage("CVE-2012-3386");
}

@Test
void shouldHandleEmptyResultsJenkins67296() {
Report report = parse("issue67296.json");

assertThat(report).isEmpty();
}

@Test
void brokenInput() {
assertThatThrownBy(() -> parse("eclipse.txt")).isInstanceOf(ParsingException.class);
}

@Override
protected IssueParser createParser() {
return new YoctoScannerParser();
}
}
Loading

0 comments on commit 04c2d27

Please sign in to comment.