Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Yocto scanner #1085

Merged
merged 1 commit into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed that to https://upload.wikimedia.org/wikipedia/commons/0/00/Yocto_Project_logo.svg, otherwise the text is white on white background. It seems that the project needs to define a SVG logo that correctly assigns the colors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@uhafner very good, and sorry, you have done a fantastic job here in this plugin

}
}
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
Loading