fix: syft does not handle the case of parsing a jar with multiple poms (#2231)

---------

Signed-off-by: Colm O hEigeartaigh <coheigea@apache.org>
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
This commit is contained in:
Colm O hEigeartaigh 2023-11-01 17:10:17 +00:00 committed by GitHub
parent dc9bc58480
commit 26cdbfc299
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 346 additions and 11 deletions

View file

@ -263,21 +263,21 @@ func (j *archiveParser) guessMainPackageNameAndVersionFromPomInfo() (name, versi
pomMatches := j.fileManifest.GlobMatch(false, pomXMLGlob) pomMatches := j.fileManifest.GlobMatch(false, pomXMLGlob)
var pomPropertiesObject pkg.JavaPomProperties var pomPropertiesObject pkg.JavaPomProperties
var pomProjectObject *parsedPomProject var pomProjectObject *parsedPomProject
if len(pomPropertyMatches) == 1 || len(pomMatches) == 1 {
// we have exactly 1 pom.properties or pom.xml in the archive; assume it represents the
// package we're scanning if the names seem like a plausible match
properties, _ := pomPropertiesByParentPath(j.archivePath, j.location, pomPropertyMatches)
projects, _ := pomProjectByParentPath(j.archivePath, j.location, pomMatches)
for parentPath, propertiesObj := range properties { // Find the pom.properties/pom.xml if the names seem like a plausible match
if artifactIDMatchesFilename(propertiesObj.ArtifactID, j.fileInfo.name) { properties, _ := pomPropertiesByParentPath(j.archivePath, j.location, pomPropertyMatches)
pomPropertiesObject = propertiesObj projects, _ := pomProjectByParentPath(j.archivePath, j.location, pomMatches)
if proj, exists := projects[parentPath]; exists {
pomProjectObject = proj for parentPath, propertiesObj := range properties {
} if artifactIDMatchesFilename(propertiesObj.ArtifactID, j.fileInfo.name) {
pomPropertiesObject = propertiesObj
if proj, exists := projects[parentPath]; exists {
pomProjectObject = proj
break
} }
} }
} }
name = pomPropertiesObject.ArtifactID name = pomPropertiesObject.ArtifactID
if name == "" && pomProjectObject != nil { if name == "" && pomProjectObject != nil {
name = pomProjectObject.ArtifactID name = pomProjectObject.ArtifactID

View file

@ -1036,6 +1036,7 @@ func Test_parseJavaArchive_regressions(t *testing.T) {
fixtureName string fixtureName string
expectedPkgs []pkg.Package expectedPkgs []pkg.Package
expectedRelationships []artifact.Relationship expectedRelationships []artifact.Relationship
assignParent bool
want bool want bool
}{ }{
{ {
@ -1146,10 +1147,74 @@ func Test_parseJavaArchive_regressions(t *testing.T) {
}, },
}, },
}, },
{
name: "multiple pom for parent selection regression (pr 2231)",
fixtureName: "api-all-2.0.0-sources",
assignParent: true,
expectedPkgs: []pkg.Package{
{
Name: "api-all",
Version: "2.0.0",
Type: pkg.JavaPkg,
Language: pkg.Java,
PURL: "pkg:maven/org.apache.directory.api/api-all@2.0.0",
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/jar-metadata/cache/api-all-2.0.0-sources.jar")),
Metadata: pkg.JavaArchive{
VirtualPath: "test-fixtures/jar-metadata/cache/api-all-2.0.0-sources.jar",
Manifest: &pkg.JavaManifest{
Main: map[string]string{
"Build-Jdk": "1.8.0_191",
"Built-By": "elecharny",
"Created-By": "Apache Maven 3.6.0",
"Manifest-Version": "1.0",
},
},
PomProperties: &pkg.JavaPomProperties{
Path: "META-INF/maven/org.apache.directory.api/api-all/pom.properties",
GroupID: "org.apache.directory.api",
ArtifactID: "api-all",
Version: "2.0.0",
},
},
},
{
Name: "api-asn1-api",
Version: "2.0.0",
PURL: "pkg:maven/org.apache.directory.api/api-asn1-api@2.0.0",
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/jar-metadata/cache/api-all-2.0.0-sources.jar")),
Type: pkg.JavaPkg,
Language: pkg.Java,
Metadata: pkg.JavaArchive{
VirtualPath: "test-fixtures/jar-metadata/cache/api-all-2.0.0-sources.jar:org.apache.directory.api:api-asn1-api",
PomProperties: &pkg.JavaPomProperties{
Path: "META-INF/maven/org.apache.directory.api/api-asn1-api/pom.properties",
GroupID: "org.apache.directory.api",
ArtifactID: "api-asn1-api",
Version: "2.0.0",
},
PomProject: &pkg.JavaPomProject{
Path: "META-INF/maven/org.apache.directory.api/api-asn1-api/pom.xml",
ArtifactID: "api-asn1-api",
Name: "Apache Directory API ASN.1 API",
Description: "ASN.1 API",
Parent: &pkg.JavaPomParent{
GroupID: "org.apache.directory.api",
ArtifactID: "api-asn1-parent",
Version: "2.0.0",
},
},
Parent: nil,
},
},
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
gap := newGenericArchiveParserAdapter(Config{}) gap := newGenericArchiveParserAdapter(Config{})
if tt.assignParent {
assignParent(&tt.expectedPkgs[0], tt.expectedPkgs[1:]...)
}
pkgtest.NewCatalogTester(). pkgtest.NewCatalogTester().
FromFile(t, generateJavaMetadataJarFixture(t, tt.fixtureName)). FromFile(t, generateJavaMetadataJarFixture(t, tt.fixtureName)).
Expects(tt.expectedPkgs, tt.expectedRelationships). Expects(tt.expectedPkgs, tt.expectedRelationships).
@ -1159,6 +1224,18 @@ func Test_parseJavaArchive_regressions(t *testing.T) {
} }
} }
func assignParent(parent *pkg.Package, childPackages ...pkg.Package) {
for i, jp := range childPackages {
if v, ok := jp.Metadata.(pkg.JavaArchive); ok {
parent := *parent
// PURL are not calculated after the fact for parent
parent.PURL = ""
v.Parent = &parent
childPackages[i].Metadata = v
}
}
}
func generateJavaMetadataJarFixture(t *testing.T, fixtureName string) string { func generateJavaMetadataJarFixture(t *testing.T, fixtureName string) string {
fixturePath := filepath.Join("test-fixtures/jar-metadata/cache/", fixtureName+".jar") fixturePath := filepath.Join("test-fixtures/jar-metadata/cache/", fixtureName+".jar")
if _, err := os.Stat(fixturePath); !os.IsNotExist(err) { if _, err := os.Stat(fixturePath); !os.IsNotExist(err) {

View file

@ -3,6 +3,7 @@ CACHE_PATH = $(shell pwd)/cache
JACKSON_CORE = jackson-core-2.15.2 JACKSON_CORE = jackson-core-2.15.2
SBT_JACKSON_CORE = com.fasterxml.jackson.core.jackson-core-2.15.2 SBT_JACKSON_CORE = com.fasterxml.jackson.core.jackson-core-2.15.2
API_ALL_SOURCES = api-all-2.0.0-sources
$(CACHE_DIR): $(CACHE_DIR):
mkdir -p $(CACHE_DIR) mkdir -p $(CACHE_DIR)
@ -12,3 +13,6 @@ $(CACHE_DIR)/$(JACKSON_CORE).jar: $(CACHE_DIR)
$(CACHE_DIR)/$(SBT_JACKSON_CORE).jar: $(CACHE_DIR) $(CACHE_DIR)/$(SBT_JACKSON_CORE).jar: $(CACHE_DIR)
cd $(SBT_JACKSON_CORE) && zip -r $(CACHE_PATH)/$(SBT_JACKSON_CORE).jar . cd $(SBT_JACKSON_CORE) && zip -r $(CACHE_PATH)/$(SBT_JACKSON_CORE).jar .
$(CACHE_DIR)/$(API_ALL_SOURCES).jar: $(CACHE_DIR)
cd $(API_ALL_SOURCES) && zip -r $(CACHE_PATH)/$(API_ALL_SOURCES).jar .

View file

@ -3,3 +3,11 @@
Each directory is the name of a jar to be created (simply a zip) based on the contents of the directory. Each directory is the name of a jar to be created (simply a zip) based on the contents of the directory.
This prevents us from having to create real jars by hand or keep binaries in the repo. This also means we dont need the This prevents us from having to create real jars by hand or keep binaries in the repo. This also means we dont need the
entire jar, only the necessary metadata for testing. entire jar, only the necessary metadata for testing.
### api-all-2.0.0-sources
This fixture is built to simulate the case where we have a jar with multiple pom files discovered when trying to determine the parent.
This is a valid case, but not one that we covered before [PR 2231](https://github.com/anchore/syft/pull/2231)
### jackson-core-2.15.2
These two fixtures are built to simulate the case where we would have a duplicate jar
regression as seen in [issue #2130](https://github.com/anchore/syft/issues/2130)

View file

@ -0,0 +1,4 @@
Manifest-Version: 1.0
Built-By: elecharny
Created-By: Apache Maven 3.6.0
Build-Jdk: 1.8.0_191

View file

@ -0,0 +1,4 @@
#Created by Apache Maven 3.6.0
version=2.0.0
groupId=org.apache.directory.api
artifactId=api-all

View file

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.directory.api</groupId>
<artifactId>api-parent</artifactId>
<version>2.0.0</version>
</parent>
<artifactId>api-all</artifactId>
<name>Apache Directory API All</name>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-asn1-api</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-asn1-ber</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-dsml-engine</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-dsml-parser</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-i18n</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-ldap-client-api</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-ldap-codec-core</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-ldap-codec-standalone</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-ldap-extras-aci</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-ldap-extras-codec</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-ldap-extras-codec-api</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-ldap-extras-sp</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-ldap-extras-trigger</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-ldap-extras-util</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-ldap-model</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-ldap-net-mina</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-ldap-schema-converter</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-ldap-schema-data</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-util</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<includes>
<include>${project.groupId}</include>
</includes>
</artifactSet>
<promoteTransitiveDependencies>true</promoteTransitiveDependencies>
<createSourcesJar>true</createSourcesJar>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,4 @@
#Created by Apache Maven 3.6.0
version=2.0.0
groupId=org.apache.directory.api
artifactId=api-asn1-api

View file

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.directory.api</groupId>
<artifactId>api-asn1-parent</artifactId>
<version>2.0.0</version>
</parent>
<artifactId>api-asn1-api</artifactId>
<name>Apache Directory API ASN.1 API</name>
<packaging>bundle</packaging>
<description>ASN.1 API</description>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api-i18n</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>META-INF/MANIFEST.MF</manifestFile>
<addMavenDescriptor>false</addMavenDescriptor>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<inherited>true</inherited>
<extensions>true</extensions>
<configuration>
<manifestLocation>META-INF</manifestLocation>
<instructions>
<Bundle-SymbolicName>${project.groupId}.asn1.api</Bundle-SymbolicName>
<Export-Package>
org.apache.directory.api.asn1;version=${project.version};-noimport:=true,
org.apache.directory.api.asn1.util;version=${project.version};-noimport:=true
</Export-Package>
<Import-Package>
org.apache.directory.api.i18n;version=${project.version}
</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>