mirror of
https://github.com/ysoftdevs/odc-yocto-analyzer.git
synced 2026-01-11 14:30:51 +01:00
Initial commit
This commit is contained in:
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Maven template
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
|
||||
# Avoid ignoring Maven wrapper jar file (.jar files are usually ignored)
|
||||
!/.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
|
||||
.idea
|
||||
sample
|
||||
*.iml
|
||||
17
README.md
Normal file
17
README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
A plugin for OWASP Dependency Check that analyzes IPK files from YOCTO.
|
||||
|
||||
Useful for finding known vulnerabilities and licenses.
|
||||
|
||||
The plugin automatically suppresses CVEs mentioned in source section, as it expects any mention of a CVE in this section is a patch fixing the CVE.
|
||||
|
||||
## Requirements
|
||||
|
||||
This plugin calls `tar` and `ar` utilities. You need them on $PATH.
|
||||
|
||||
Tested with Debian, but it will likely work with other distributions or even with Windows if these two utilities are on $PATH (or %PATH% :) ).
|
||||
|
||||
## Howto
|
||||
|
||||
1. Build JAR file using `mvn package`.
|
||||
2. Add the JAR file to `plugins` directory of OWASP Dependency Check (CLI version).
|
||||
3. Run the ODC on IPK files with com.ysoft.yocto.enabled=true.
|
||||
49
pom.xml
Normal file
49
pom.xml
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.ysoft.security</groupId>
|
||||
<artifactId>odc-yocto-analyzer</artifactId>
|
||||
<version>1.2.1</version>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.owasp</groupId>
|
||||
<artifactId>dependency-check-core</artifactId>
|
||||
<version>5.2.4</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>21.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,182 @@
|
||||
package com.ysoft.security.odc.yocto;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.owasp.dependencycheck.analyzer.AbstractFileTypeAnalyzer;
|
||||
import org.owasp.dependencycheck.utils.InvalidSettingException;
|
||||
import org.owasp.dependencycheck.utils.Settings;
|
||||
|
||||
import static com.ysoft.security.odc.yocto.ControlFileParser.parseControlFile;
|
||||
|
||||
abstract class AbstractYoctoAnalyzer extends AbstractFileTypeAnalyzer {
|
||||
|
||||
public static final String YOCTO_ANALYZER_KEY = "com.ysoft.yocto.enabled";
|
||||
public static final String YOCTO_ANALYZER_EXPERIMENTAL_DEBIAN_KEY = "com.ysoft.yocto.experimental.debian.enabled";
|
||||
public static final String IPK_SOURCE = "ipk";
|
||||
|
||||
protected FileFilter getFileFilter() {
|
||||
return f -> {
|
||||
final String name = f.getName().toLowerCase();
|
||||
return name.endsWith(".ipk") || (isExperimentalDebEnabled() && name.endsWith(".deb"));
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAnalyzerEnabledSettingKey() {
|
||||
return YOCTO_ANALYZER_KEY;
|
||||
}
|
||||
|
||||
private void throwChecked(Throwable e) {
|
||||
this.throwChecked0(e);
|
||||
}
|
||||
|
||||
protected boolean isExperimentalDebEnabled() {
|
||||
return getSettings().getBoolean(YOCTO_ANALYZER_EXPERIMENTAL_DEBIAN_KEY, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends RuntimeException> void throwChecked0(Throwable e) {
|
||||
throw (T) e;
|
||||
}
|
||||
|
||||
|
||||
// In future, we might want to be more universal: https://blog.philippklaus.de/2011/04/have-a-look-into-an-ipk-file-used-by-the-ipkg-or-opkg-manager
|
||||
|
||||
protected IpkFile parseIpkFile(File ipkFile) throws IOException {
|
||||
return new IpkFile(parseIpkManifest(ipkFile));//listIpkFiles(ipkFile));
|
||||
}
|
||||
|
||||
private Set<String> listIpkFiles(File ipkFile) throws IOException {
|
||||
// TODO: add support for xz, bz etc.
|
||||
final Process arProcess = createArExtractionProcess(ipkFile.getAbsolutePath(), "data.tar.gz");
|
||||
try{
|
||||
final Future<String> arStderrFuture = processStream(arProcess.getErrorStream());
|
||||
final Process tarProcess = new ProcessBuilder("tar", "-tz").start();
|
||||
try {
|
||||
final Future<String> tarStderrFuture = processStream(tarProcess.getErrorStream());
|
||||
final Future<Void> copyResult = pipe(arProcess.getInputStream(), tarProcess.getOutputStream());
|
||||
final Set<String> fileNames = new HashSet<>();
|
||||
try(final BufferedReader reader = new BufferedReader(new InputStreamReader(tarProcess.getInputStream()))){
|
||||
String line;
|
||||
while((line = reader.readLine()) != null){
|
||||
fileNames.add(line);
|
||||
}
|
||||
}
|
||||
copyResult.get(); // this throws exception if pipe has failed
|
||||
final int tarReturnCode = tarProcess.waitFor();
|
||||
final int arReturnCode = arProcess.waitFor();
|
||||
checkStderr(tarStderrFuture, "tar");
|
||||
checkStderr(arStderrFuture, "ar");
|
||||
if (tarReturnCode != 0 || arReturnCode != 0) {
|
||||
throw new IOException("Bad return code (tar: " + tarReturnCode + ", ar: " + arReturnCode + ")");
|
||||
}
|
||||
return fileNames;
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new IOException(e);
|
||||
} finally {
|
||||
tarProcess.destroyForcibly();
|
||||
}
|
||||
}finally{
|
||||
arProcess.destroyForcibly();
|
||||
}
|
||||
}
|
||||
|
||||
private IpkManifest parseIpkManifest(File ipkFile) throws IOException {
|
||||
final Process arProcess = createArExtractionProcess(ipkFile.getAbsolutePath(), "control.tar.gz");
|
||||
try{
|
||||
final Future<String> arStderrFuture = processStream(arProcess.getErrorStream());
|
||||
final Process tarProcess = new ProcessBuilder("tar", "-xzO", "./control").start();
|
||||
try{
|
||||
final Future<String> tarStderrFuture = processStream(tarProcess.getErrorStream());
|
||||
final Future<Void> copyResult = pipe(arProcess.getInputStream(), tarProcess.getOutputStream());
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
int len;
|
||||
final byte[] buff = new byte[1024];
|
||||
while((len = tarProcess.getInputStream().read(buff)) != -1){
|
||||
baos.write(buff, 0, len);
|
||||
}
|
||||
copyResult.get(); // this throws exception if pipe has failed
|
||||
final int tarReturnCode = tarProcess.waitFor();
|
||||
final int arReturnCode = arProcess.waitFor();
|
||||
checkStderr(tarStderrFuture, "tar");
|
||||
checkStderr(arStderrFuture, "ar");
|
||||
if(tarReturnCode != 0 || arReturnCode != 0){
|
||||
throw new IOException("Bad return code (tar: "+tarReturnCode+", ar: "+arReturnCode+")");
|
||||
}
|
||||
return new IpkManifest(parseControlFile(baos.toString("utf-8")));
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new IOException(e);
|
||||
} finally {
|
||||
tarProcess.destroyForcibly();
|
||||
}
|
||||
}finally{
|
||||
arProcess.destroyForcibly();
|
||||
}
|
||||
}
|
||||
|
||||
private Process createArExtractionProcess(String arPath, String file) throws IOException {
|
||||
return new ProcessBuilder("ar", "p", "--", arPath, file).start();
|
||||
}
|
||||
|
||||
private void checkStderr(Future<String> stderrFuture, String name) throws ExecutionException, InterruptedException, IOException {
|
||||
final String result = stderrFuture.get();
|
||||
if(!result.equals("")){
|
||||
throw new IOException("Process "+name+" has written something to stderr: "+result);
|
||||
}
|
||||
}
|
||||
|
||||
private Future<String> processStream(InputStream inputStream) {
|
||||
final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
try {
|
||||
return executor.submit(() -> {
|
||||
final InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
||||
final char[] buff = new char[1024];
|
||||
int len;
|
||||
final StringBuilder out = new StringBuilder();
|
||||
while ((len = reader.read(buff)) != -1){
|
||||
out.append(buff, 0, len);
|
||||
}
|
||||
return out.toString();
|
||||
});
|
||||
}finally {
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected Future<Void> pipe(InputStream in, OutputStream out){
|
||||
final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
try{
|
||||
return executor.submit(() -> {
|
||||
int len;
|
||||
final byte[] buff = new byte[1024];
|
||||
try {
|
||||
while ((len = in.read(buff)) != -1) {
|
||||
out.write(buff, 0, len);
|
||||
}
|
||||
out.close();
|
||||
} catch (IOException e){
|
||||
throwChecked(e);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}finally {
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.ysoft.security.odc.yocto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class ControlFileLineBuilder{
|
||||
|
||||
private final List<String> lines = new ArrayList<>();
|
||||
private final Key key;
|
||||
|
||||
public ControlFileLineBuilder(String line) throws IOException {
|
||||
final int pos = line.indexOf(':');
|
||||
if(pos == -1){
|
||||
throw new IOException("Bad line: expected colon: "+line);
|
||||
}
|
||||
key = new Key(line.substring(0, pos));
|
||||
addLine(line.substring(pos+1));
|
||||
}
|
||||
|
||||
private void addLine(String s) {
|
||||
lines.add(s.trim());
|
||||
}
|
||||
|
||||
public Line build() {
|
||||
return new Line(key, lines.stream().collect(Collectors.joining("\n")));
|
||||
}
|
||||
|
||||
public void add(String line) {
|
||||
addLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
class Line{
|
||||
private final Key key;
|
||||
private final String value;
|
||||
public Line(Key key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
public Key getKey() {
|
||||
return key;
|
||||
}
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
class ControlFileParagraphBuilder{
|
||||
|
||||
private ControlFileLineBuilder lineBuilder;
|
||||
|
||||
private Map<Key, String> map = new HashMap<>();
|
||||
|
||||
public void addLine(String line) throws IOException {
|
||||
if(line.startsWith(" ") || line.startsWith("\t")){
|
||||
if(lineBuilder == null) {
|
||||
throw new IOException("Bad control file: paragraph cannot start with whitespace.");
|
||||
} else {
|
||||
lineBuilder.add(line);
|
||||
}
|
||||
}else{
|
||||
finishCurrentLine();
|
||||
lineBuilder = new ControlFileLineBuilder(line);
|
||||
}
|
||||
}
|
||||
|
||||
private void finishCurrentLine() throws IOException {
|
||||
if(lineBuilder != null){
|
||||
add(lineBuilder.build());
|
||||
lineBuilder = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void add(Line line) throws IOException {
|
||||
if(map.containsKey(line.getKey())){
|
||||
throw new IOException("Duplicate key: "+line.getKey());
|
||||
}
|
||||
map.put(line.getKey(), line.getValue());
|
||||
}
|
||||
|
||||
public Map<Key, String> build() throws IOException {
|
||||
finishCurrentLine();
|
||||
final Map<Key, String> res = Collections.unmodifiableMap(map);
|
||||
map = null; // don't allow reuse
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
public class ControlFileParser {
|
||||
|
||||
/**
|
||||
* Works according specification https://www.debian.org/doc/debian-policy/ch-controlfields.html with some limitations:
|
||||
* * parses only one paragraph
|
||||
* @param s
|
||||
* @return
|
||||
*/
|
||||
public static Map<Key, String> parseControlFile(String s) throws IOException {
|
||||
final ControlFileParagraphBuilder builder = new ControlFileParagraphBuilder();
|
||||
for(final String line : s.split("\n")){
|
||||
builder.addLine(line);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
67
src/main/java/com/ysoft/security/odc/yocto/IpkFile.java
Normal file
67
src/main/java/com/ysoft/security/odc/yocto/IpkFile.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package com.ysoft.security.odc.yocto;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class IpkFile {
|
||||
|
||||
private final IpkManifest manifest;
|
||||
//private final Set<String> fileNames;
|
||||
|
||||
public IpkFile(IpkManifest manifest/*, Set<String> fileNames*/) {
|
||||
this.manifest = manifest;
|
||||
//this.fileNames = fileNames;
|
||||
}
|
||||
|
||||
public IpkManifest getManifest() {
|
||||
return manifest;
|
||||
}
|
||||
|
||||
/*public Set<String> getFileNames() {
|
||||
return fileNames;
|
||||
}*/
|
||||
|
||||
public Set<String> getFixedCves() {
|
||||
return manifest.getFixedCves();
|
||||
}
|
||||
|
||||
public String getPackageName(){
|
||||
return manifest.get(IpkManifest.KEY_PACKAGE);
|
||||
}
|
||||
|
||||
public String getVersion(){
|
||||
return manifest.get(IpkManifest.KEY_VERSION);
|
||||
}
|
||||
|
||||
public String getDescription(){
|
||||
return manifest.get(IpkManifest.KEY_DESCRIPTION);
|
||||
}
|
||||
|
||||
public String getLicense(){
|
||||
return manifest.get(IpkManifest.KEY_LICENSE);
|
||||
}
|
||||
|
||||
public String getHomepage(){
|
||||
return manifest.get(IpkManifest.KEY_HOMEPAGE);
|
||||
}
|
||||
|
||||
public String getOE(){
|
||||
return manifest.get(IpkManifest.KEY_OE);
|
||||
}
|
||||
|
||||
public List<String> getSources(boolean experimentalDebEnabled){
|
||||
final String source = experimentalDebEnabled
|
||||
? manifest.get(IpkManifest.KEY_SOURCE, "")
|
||||
: manifest.get(IpkManifest.KEY_SOURCE);
|
||||
return Arrays.asList(source.split("\\s+"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IpkFile{" +
|
||||
"manifest=" + manifest +
|
||||
//", fileNames=" + fileNames +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
50
src/main/java/com/ysoft/security/odc/yocto/IpkManifest.java
Normal file
50
src/main/java/com/ysoft/security/odc/yocto/IpkManifest.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package com.ysoft.security.odc.yocto;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class IpkManifest {
|
||||
|
||||
public static final Key KEY_PACKAGE = new Key("Package");
|
||||
public static final Key KEY_VERSION = new Key("Version");
|
||||
public static final Key KEY_DESCRIPTION = new Key("Description");
|
||||
public static final Key KEY_LICENSE = new Key("License");
|
||||
public static final Key KEY_HOMEPAGE = new Key("Homepage");
|
||||
public static final Key KEY_SOURCE = new Key("Source");
|
||||
public static final Key KEY_OE = new Key("OE");
|
||||
|
||||
private static final Pattern CVE_REGEX = Pattern.compile("CVE-[0-9]{4}-[0-9]{4,}"); // slightly more permissible than https://cve.mitre.org/cve/identifiers/syntaxchange.html (leading zeros, number length)
|
||||
private final Map<Key, String> manifest;
|
||||
|
||||
public IpkManifest(Map<Key, String> manifest) {
|
||||
this.manifest = manifest;
|
||||
}
|
||||
|
||||
public String get(Key key){
|
||||
return manifest.get(key);
|
||||
}
|
||||
|
||||
public String get(Key key, String defaultValue){
|
||||
return manifest.getOrDefault(key, defaultValue);
|
||||
}
|
||||
|
||||
public Set<String> getFixedCves() {
|
||||
final Matcher matcher = CVE_REGEX.matcher(manifest.getOrDefault(KEY_SOURCE, ""));
|
||||
final Set<String> set = new HashSet<>();
|
||||
while(matcher.find()){
|
||||
set.add(matcher.group());
|
||||
}
|
||||
return Collections.unmodifiableSet(set);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IpkManifest{" +
|
||||
"manifest=" + manifest +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
40
src/main/java/com/ysoft/security/odc/yocto/Key.java
Normal file
40
src/main/java/com/ysoft/security/odc/yocto/Key.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package com.ysoft.security.odc.yocto;
|
||||
|
||||
public class Key{
|
||||
private final String key;
|
||||
private final String canonicalKey;
|
||||
|
||||
public Key(String key) {
|
||||
this.key = key;
|
||||
this.canonicalKey = key.toLowerCase();
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Key key = (Key) o;
|
||||
|
||||
return canonicalKey.equals(key.canonicalKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return canonicalKey.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Key(" + key + ")";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.ysoft.security.odc.yocto;
|
||||
|
||||
import com.github.packageurl.MalformedPackageURLException;
|
||||
import org.owasp.dependencycheck.Engine;
|
||||
import org.owasp.dependencycheck.analyzer.AnalysisPhase;
|
||||
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
|
||||
import org.owasp.dependencycheck.dependency.Confidence;
|
||||
import org.owasp.dependencycheck.dependency.Dependency;
|
||||
import org.owasp.dependencycheck.dependency.naming.PurlIdentifier;
|
||||
import org.owasp.dependencycheck.exception.InitializationException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.owasp.dependencycheck.dependency.EvidenceType.*;
|
||||
|
||||
public class YoctoAnalyzer extends AbstractYoctoAnalyzer {
|
||||
|
||||
public AnalysisPhase getAnalysisPhase() {
|
||||
return AnalysisPhase.INFORMATION_COLLECTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepareFileTypeAnalyzer(Engine engine) throws InitializationException {}
|
||||
|
||||
public String getName() {
|
||||
return "YOCTO analyzer";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
|
||||
try {
|
||||
final IpkFile ipkFile = parseIpkFile(dependency.getActualFile());
|
||||
|
||||
dependency.addSoftwareIdentifier(new PurlIdentifier("yocto", ipkFile.getPackageName(), ipkFile.getVersion(), Confidence.HIGHEST));
|
||||
dependency.addEvidence(PRODUCT, IPK_SOURCE, "package name", ipkFile.getPackageName(), Confidence.HIGHEST);
|
||||
dependency.addEvidence(VENDOR, IPK_SOURCE, "package name", ipkFile.getPackageName()+"_project", Confidence.LOW);
|
||||
final String oe = ipkFile.getOE();
|
||||
final List<String> sources = ipkFile.getSources(isExperimentalDebEnabled());
|
||||
if(!sources.isEmpty()){
|
||||
dependency.addEvidence(VENDOR, IPK_SOURCE, "source url", sources.get(0), Confidence.MEDIUM);
|
||||
//productEvidence.addEvidence(IPK_SOURCE, "source url", sources.get(0), Confidence.MEDIUM);
|
||||
}
|
||||
if(oe != null){
|
||||
dependency.addEvidence(PRODUCT, IPK_SOURCE, "name", oe, Confidence.HIGHEST);
|
||||
dependency.addEvidence(VENDOR, IPK_SOURCE, "name", oe+"_project", Confidence.LOW);
|
||||
}
|
||||
dependency.addEvidence(VERSION, IPK_SOURCE, "package version", ipkFile.getVersion(), Confidence.HIGH);
|
||||
dependency.addEvidence(VERSION, IPK_SOURCE, "version", ipkFile.getVersion().replaceAll("-r[0-9]+$", ""), Confidence.HIGHEST);
|
||||
final String homepage = ipkFile.getHomepage();
|
||||
dependency.setDescription(ipkFile.getDescription());
|
||||
dependency.setLicense(ipkFile.getLicense());
|
||||
if(homepage != null && !homepage.equals("")) {
|
||||
dependency.addEvidence(VENDOR, IPK_SOURCE, "organization url", homepage, Confidence.HIGHEST);
|
||||
dependency.addEvidence(PRODUCT, IPK_SOURCE, "organization url", homepage, Confidence.HIGHEST);
|
||||
}
|
||||
} catch (IOException | MalformedPackageURLException e) {
|
||||
throw new AnalysisException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.ysoft.security.odc.yocto;
|
||||
|
||||
import org.owasp.dependencycheck.Engine;
|
||||
import org.owasp.dependencycheck.analyzer.AbstractAnalyzer;
|
||||
import org.owasp.dependencycheck.analyzer.AnalysisPhase;
|
||||
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
|
||||
import org.owasp.dependencycheck.dependency.Dependency;
|
||||
import org.owasp.dependencycheck.dependency.EvidenceType;
|
||||
|
||||
import static org.owasp.dependencycheck.dependency.EvidenceType.*;
|
||||
|
||||
/**
|
||||
* FileNameAnalyzer tries to get the version from file name. The problem is that it includes “-r0” or something similar from distribution, which is not
|
||||
* much relevant for ODC. This analyzer removes the version added by FileNameAnalyzer if YOCTO analyzer has added some version evidence.
|
||||
*/
|
||||
public class YoctoFilenameVersionSuppressionAnalyzer extends AbstractAnalyzer {
|
||||
|
||||
@Override
|
||||
protected String getAnalyzerEnabledSettingKey() {
|
||||
return AbstractYoctoAnalyzer.YOCTO_ANALYZER_KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "YOCTO filename version suppression";
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnalysisPhase getAnalysisPhase() {
|
||||
return AnalysisPhase.POST_INFORMATION_COLLECTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
|
||||
cleanEvidence(dependency, VERSION);
|
||||
cleanEvidence(dependency, PRODUCT);
|
||||
cleanEvidence(dependency, VENDOR);
|
||||
}
|
||||
|
||||
private static void cleanEvidence(Dependency dependency, EvidenceType evidenceType) {
|
||||
if(dependency.getEvidence(evidenceType).stream().anyMatch(x -> x.getSource().equals(AbstractYoctoAnalyzer.IPK_SOURCE))){
|
||||
dependency.getEvidence(evidenceType).stream().filter(item -> item.getSource().equals("file")).forEach(evidence -> dependency.removeEvidence(evidenceType, evidence));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.ysoft.security.odc.yocto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import org.owasp.dependencycheck.Engine;
|
||||
import org.owasp.dependencycheck.analyzer.AnalysisPhase;
|
||||
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
|
||||
import org.owasp.dependencycheck.dependency.Dependency;
|
||||
import org.owasp.dependencycheck.dependency.Vulnerability;
|
||||
import org.owasp.dependencycheck.exception.InitializationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class YoctoPatchedVulnerabilitySuppressionAnalyzer extends AbstractYoctoAnalyzer {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(YoctoPatchedVulnerabilitySuppressionAnalyzer.class);
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "YOCTO suppression analyzer";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepareFileTypeAnalyzer(Engine engine) throws InitializationException {}
|
||||
|
||||
@Override
|
||||
public AnalysisPhase getAnalysisPhase() {
|
||||
return AnalysisPhase.POST_FINDING_ANALYSIS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
|
||||
try{
|
||||
final IpkFile ipkFile = parseIpkFile(dependency.getActualFile());
|
||||
final Set<String> remainingCvesForSuppression = new HashSet<>(ipkFile.getFixedCves());
|
||||
for (final Vulnerability vulnerability : new HashSet<>(dependency.getVulnerabilities())) { // For some reason, this API does not return a copy, which causes ConcurrentModificationException.
|
||||
final String vulnerabilityName = vulnerability.getName();
|
||||
if (remainingCvesForSuppression.contains(vulnerabilityName)) {
|
||||
dependency.removeVulnerability(vulnerability);
|
||||
dependency.addSuppressedVulnerability(vulnerability);
|
||||
remainingCvesForSuppression.remove(vulnerabilityName);
|
||||
}
|
||||
}
|
||||
if(!remainingCvesForSuppression.isEmpty()){
|
||||
LOGGER.warn("Dependency {} has some undetected vulnerabilities to suppress that were not matched: {}", dependency.getActualFilePath(), remainingCvesForSuppression);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AnalysisException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
com.ysoft.security.odc.yocto.YoctoAnalyzer
|
||||
com.ysoft.security.odc.yocto.YoctoPatchedVulnerabilitySuppressionAnalyzer
|
||||
com.ysoft.security.odc.yocto.YoctoFilenameVersionSuppressionAnalyzer
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.ysoft.security.odc.yocto;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class ControlFileParserTest {
|
||||
|
||||
@Test
|
||||
public void parseControlFile() throws IOException {
|
||||
try(final InputStream in = getClass().getResourceAsStream("/example.control")) {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
final byte[] buff = new byte[1024];
|
||||
int len;
|
||||
while((len = in.read(buff)) != -1){
|
||||
baos.write(buff, 0, len);
|
||||
}
|
||||
final String s = baos.toString("utf-8");
|
||||
final Map<Key, String> res = ControlFileParser.parseControlFile(s);
|
||||
// single-line
|
||||
assertEquals(res.get(new Key("Package")), "augeas-lenses");
|
||||
// case sensitivity
|
||||
assertEquals(res.get(new Key("packAge")), "augeas-lenses");
|
||||
// missint
|
||||
assertEquals(res.get(new Key("hackage")), null);
|
||||
// multiline
|
||||
assertEquals(res.get(new Key("Description")), "Augeas configuration API\n" + "Augeas configuration API.");
|
||||
// last line
|
||||
assertEquals(res.get(new Key("Source")), "http://download.augeas.net/augeas-1.4.0.tar.gz file://add-missing-argz-conditional"
|
||||
+ ".patch file://sepbuildfix.patch file://0001-Unset-need_charset_alias-when-building-for-musl.patch");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.ysoft.security.odc.yocto;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class IpkManifestTest {
|
||||
|
||||
@Test
|
||||
public void testCveParseXinetd(){
|
||||
final IpkManifest ipkManifest = new IpkManifest(ImmutableMap.of(new Key("Source"), "git://github.com/xinetd-org/xinetd.git;protocol=https "
|
||||
+ "file://xinetd.init file://xinetd.conf file://xinetd.default file://Various-fixes-from-the-previous-maintainer.patch "
|
||||
+ "file://Disable-services-from-inetd.conf-if-a-service-with-t.patch file://xinetd-should-be-able-to-listen-on-IPv6-even-in-ine.patch "
|
||||
+ "file://xinetd-CVE-2013-4342.patch file://xinetd.service"));
|
||||
assertEquals(ImmutableSet.of("CVE-2013-4342"), ipkManifest.getFixedCves());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCveParseOpenSsh(){
|
||||
final IpkManifest ipkManifest = new IpkManifest(ImmutableMap.of(new Key("Source"), "Source: ftp://ftp.openbsd"
|
||||
+ ".org/pub/OpenBSD/OpenSSH/portable/openssh-7.1p2.tar.gz file://sshd_config file://ssh_config file://init file://sshd file://sshd"
|
||||
+ ".socket file://sshd@.service file://sshdgenkeys.service file://volatiles.99_sshd file://add-test-support-for-busybox.patch "
|
||||
+ "file://run-ptest file://CVE-2016-1907_upstream_commit.patch file://CVE-2016-1907_2.patch file://CVE-2016-1907_3.patch "
|
||||
+ "file://CVE-2016-3115.patch file://sshdgenkeys.service\n"
|
||||
+ "/home/user/projects/odc-yocto-analyzer/sample/cortexa9t2hf-vfp-neon/openssh-ssh_7.1p2-r0_cortexa9t2hf-vfp-neon.ipk"));
|
||||
assertEquals(ImmutableSet.of("CVE-2016-1907", "CVE-2016-1907", "CVE-2016-1907", "CVE-2016-3115"), ipkManifest.getFixedCves());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCveParseLibXml2(){
|
||||
final IpkManifest ipkManifest = new IpkManifest(ImmutableMap.of(new Key("Source"), "Source: ftp://xmlsoft.org/libxml2/libxml2-2.9.2.tar.gz;"
|
||||
+ "name=libtar file://libxml-64bit.patch file://ansidecl.patch file://runtest.patch file://run-ptest file://libxml2-CVE-2014-0191-fix"
|
||||
+ ".patch file://python-sitepackages-dir.patch file://libxml-m4-use-pkgconfig.patch file://configure.ac-fix-cross-compiling-warning"
|
||||
+ ".patch file://0001-CVE-2015-1819-Enforce-the-reader-to-run-in-constant-.patch "
|
||||
+ "file://CVE-2015-7941-1-Stop-parsing-on-entities-boundaries-errors.patch "
|
||||
+ "file://CVE-2015-7941-2-Cleanup-conditional-section-error-handling.patch "
|
||||
+ "file://CVE-2015-8317-Fail-parsing-early-on-if-encoding-conversion-failed.patch "
|
||||
+ "file://CVE-2015-7942-Another-variation-of-overflow-in-Conditional-section.patch "
|
||||
+ "file://CVE-2015-7942-2-Fix-an-error-in-previous-Conditional-section-patch.patch "
|
||||
+ "file://0001-CVE-2015-8035-Fix-XZ-compression-support-loop.patch "
|
||||
+ "file://CVE-2015-7498-Avoid-processing-entities-after-encoding-conversion-.patch "
|
||||
+ "file://0001-CVE-2015-7497-Avoid-an-heap-buffer-overflow-in-xmlDi.patch file://CVE-2015-7499-1-Add-xmlHaltParser-to-stop-the-parser"
|
||||
+ ".patch file://CVE-2015-7499-2-Detect-incoherency-on-GROW.patch file://0001-Fix-a-bug-on-name-parsing-at-the-end-of-current-inpu"
|
||||
+ ".patch file://0001-CVE-2015-7500-Fix-memory-access-error-due-to-incorre.patch "
|
||||
+ "file://0001-CVE-2015-8242-Buffer-overead-with-HTML-parser-in-pus.patch file://0001-CVE-2015-5312-Another-entity-expansion-issue"
|
||||
+ ".patch file://CVE-2015-8241.patch file://CVE-2015-8710.patch http://www.w3.org/XML/Test/xmlts20080827.tar.gz;name=testtar "
|
||||
+ "file://72a46a519ce7326d9a00f0b6a7f2a8e958cd1675.patch file://0001-threads-Define-pthread-definitions-for-glibc-complia.patch"));
|
||||
assertEquals(ImmutableSet.of("CVE-2014-0191", "CVE-2015-1819", "CVE-2015-7941", "CVE-2015-8317", "CVE-2015-7942", "CVE-2015-8035",
|
||||
"CVE-2015-7498", "CVE-2015-7497", "CVE-2015-7499", "CVE-2015-7499", "CVE-2015-7500", "CVE-2015-8242", "CVE-2015-5312",
|
||||
"CVE-2015-8241", "CVE-2015-8710"), ipkManifest.getFixedCves());
|
||||
}
|
||||
|
||||
}
|
||||
12
src/test/resources/example.control
Normal file
12
src/test/resources/example.control
Normal file
@@ -0,0 +1,12 @@
|
||||
Package: augeas-lenses
|
||||
Version: 1.4.0-r0
|
||||
Description: Augeas configuration API
|
||||
Augeas configuration API.
|
||||
Section: base
|
||||
Priority: optional
|
||||
Maintainer: OE-Core Developers <openembedded-core@lists.openembedded.org>
|
||||
License: LGPLv2.1+
|
||||
Architecture: cortexa9t2hf-vfp-neon
|
||||
OE: augeas
|
||||
Homepage: http://augeas.net/
|
||||
Source: http://download.augeas.net/augeas-1.4.0.tar.gz file://add-missing-argz-conditional.patch file://sepbuildfix.patch file://0001-Unset-need_charset_alias-when-building-for-musl.patch
|
||||
Reference in New Issue
Block a user