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 Sep 12, 2024
1 parent ca7733b commit ea514ba
Show file tree
Hide file tree
Showing 5 changed files with 635 additions and 0 deletions.
110 changes: 110 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,110 @@
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 (bitbake) tool.
*
* @author Michael Trimarchi
*/
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) {

Check warning on line 34 in src/main/java/edu/hm/hafner/analysis/parser/YoctoScannerParser.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 34 is only partially covered, one branch is missing
final JSONObject resourceWrapper = (JSONObject) item;
if (!resourceWrapper.isNull("issue")) {

Check warning on line 36 in src/main/java/edu/hm/hafner/analysis/parser/YoctoScannerParser.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 36 is only partially covered, one branch is missing
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) {

Check warning on line 47 in src/main/java/edu/hm/hafner/analysis/parser/YoctoScannerParser.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 47 is only partially covered, one branch is missing
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");
final String fileName = vulnerability.optString("id", "UNKNOWN");
return issueBuilder
.setType(fileName)
.setFileName(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) {

Check warning on line 75 in src/main/java/edu/hm/hafner/analysis/parser/YoctoScannerParser.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 75 is only partially covered, one branch is missing
score = vulnerability.getDouble("scorev3");
}

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

if (score >= 0 && score < 4.0) {

Check warning on line 83 in src/main/java/edu/hm/hafner/analysis/parser/YoctoScannerParser.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 83 is only partially covered, one branch is missing
return Severity.WARNING_LOW;
}
else if (score >= 4.0 && score < 7.0) {

Check warning on line 86 in src/main/java/edu/hm/hafner/analysis/parser/YoctoScannerParser.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 86 is only partially covered, one branch is missing
return Severity.WARNING_NORMAL;
}
else if (score >= 7.0 && score <= 10.0) {

Check warning on line 89 in src/main/java/edu/hm/hafner/analysis/parser/YoctoScannerParser.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 89 is only partially covered, 2 branches are missing
return Severity.WARNING_HIGH;
}

return Severity.ERROR;

Check warning on line 93 in src/main/java/edu/hm/hafner/analysis/parser/YoctoScannerParser.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 93 is not covered by tests

Check warning on line 93 in src/main/java/edu/hm/hafner/analysis/parser/YoctoScannerParser.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/edu/hm/hafner/analysis/parser/YoctoScannerParser.java#L93

Added line #L93 was not covered by tests
}

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: "), a(link).withHref(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 Michael Trimarchi
*/
class YoctoScannerDescriptor extends ParserDescriptor {
private static final String ID = "yoctocli";
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();

Check warning on line 32 in src/main/java/edu/hm/hafner/analysis/registry/YoctoScannerDescriptor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 28-32 are not covered by tests

Check warning on line 32 in src/main/java/edu/hm/hafner/analysis/registry/YoctoScannerDescriptor.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/edu/hm/hafner/analysis/registry/YoctoScannerDescriptor.java#L28-L32

Added lines #L28 - L32 were not covered by tests
}

@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";

Check warning on line 42 in src/main/java/edu/hm/hafner/analysis/registry/YoctoScannerDescriptor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 42 is not covered by tests

Check warning on line 42 in src/main/java/edu/hm/hafner/analysis/registry/YoctoScannerDescriptor.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/edu/hm/hafner/analysis/registry/YoctoScannerDescriptor.java#L42

Added line #L42 was not covered by tests
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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)
.hasFileName("acl")
.hasType("CVE-2009-4411")
.hasDescription("<div><b>Package: </b>acl</div> <div><b>Version: </b>2.3.2</div>"
+ " <div><b>Link: </b><a href=\"https://nvd.nist.gov/vuln/detail/CVE-2009-4411\""
+ ">https://nvd.nist.gov/vuln/detail/CVE-2009-4411</a></div>"
+ " <div><b>Yocto Layer: </b>meta</div> <div><b>Vector: </b>LOCAL</div> <p>"
+ "The (1) setfacl and (2) getfacl commands in XFS acl 2.2.47, when running"
+ " in recursive (-R) mode, follow symbolic links even when the --physical"
+ " (aka -P) or -L option is specified, which might allow local users to modify"
+ " the ACL for arbitrary files or directories via a symlink attack.</p>");
softly.assertThat(report.get(3))
.hasSeverity(Severity.WARNING_NORMAL)
.hasFileName("automake-native")
.hasType("CVE-2012-3386");
softly.assertThat(report.get(12))
.hasSeverity(Severity.WARNING_HIGH)
.hasFileName("avahi")
.hasType("CVE-2017-6519");
}

@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 ea514ba

Please sign in to comment.