mirror of
https://github.com/anchore/syft.git
synced 2026-11-03 16:06:36 +01:00
fix: normalize decoded FullPath
Nothing checked that a decoded `FullPath` actually extends `RealPath` with an archive chain. The SPDX
decoder sets `FullPath` to the file name even when the name doesn't end with the comment's chain, and
syft-json takes whatever is in the document. `{RealPath: "/x", FullPath: "/x"}` and `{RealPath: "/x"}`
share an ID but are different map keys, so decoded files, unknowns and locations could fail to line
up with the same coordinates built by a resolver.
The syft-json and SPDX decoders now clear a `FullPath` that equals `RealPath` or doesn't start with
`RealPath` + `!/`.
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
@@ -549,7 +549,7 @@ func coordinatesFromFile(name, comment, id string) file.Coordinates {
|
|||||||
if v, ok := strings.CutPrefix(id, layerIDCommentPrefix); ok {
|
if v, ok := strings.CutPrefix(id, layerIDCommentPrefix); ok {
|
||||||
c.FileSystemID = v
|
c.FileSystemID = v
|
||||||
}
|
}
|
||||||
return c
|
return internal.NormalizeCoordinates(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toSyftLocation(f *spdx.File) file.Location {
|
func toSyftLocation(f *spdx.File) file.Location {
|
||||||
|
|||||||
@@ -789,3 +789,12 @@ func Test_skipsPackagesWithGeneratedFromRelationship(t *testing.T) {
|
|||||||
assert.NotNil(t, s.Artifacts.Packages.Package("1"))
|
assert.NotNil(t, s.Artifacts.Packages.Package("1"))
|
||||||
assert.Nil(t, s.Artifacts.Packages.Package("1-src"))
|
assert.Nil(t, s.Artifacts.Packages.Package("1-src"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_coordinatesFromFile_nonCanonicalChainIsDropped(t *testing.T) {
|
||||||
|
// a chain the name doesn't end with leaves FullPath equal to RealPath, which must decode as a plain file
|
||||||
|
got := coordinatesFromFile("/app.jar", "archivePath: other.jar!/x", "")
|
||||||
|
assert.Equal(t, file.NewCoordinates("/app.jar", ""), got)
|
||||||
|
|
||||||
|
nested := coordinatesFromFile("/app.war!/lib/a.jar", "archivePath: lib/a.jar", "")
|
||||||
|
assert.Equal(t, file.Coordinates{RealPath: "/app.war", FullPath: "/app.war!/lib/a.jar"}, nested)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NormalizeCoordinates clears a decoded FullPath that adds no archive chain to RealPath (one equal to
|
||||||
|
// RealPath, or one not starting with RealPath and "!/"), so decoded coordinates compare equal to the
|
||||||
|
// ones a resolver builds.
|
||||||
|
func NormalizeCoordinates(c file.Coordinates) file.Coordinates {
|
||||||
|
if !strings.HasPrefix(c.FullPath, c.RealPath+file.ArchivePathSeparator) {
|
||||||
|
c.FullPath = ""
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// NormalizeLocations applies NormalizeCoordinates to each location in place.
|
||||||
|
func NormalizeLocations(locations []file.Location) []file.Location {
|
||||||
|
for i := range locations {
|
||||||
|
locations[i].Coordinates = NormalizeCoordinates(locations[i].Coordinates)
|
||||||
|
}
|
||||||
|
return locations
|
||||||
|
}
|
||||||
@@ -80,7 +80,7 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
coord := f.Location
|
coord := internal.NormalizeCoordinates(f.Location)
|
||||||
if f.Metadata != nil {
|
if f.Metadata != nil {
|
||||||
fm, err := safeFileModeConvert(f.Metadata.Mode)
|
fm, err := safeFileModeConvert(f.Metadata.Mode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -163,7 +163,7 @@ func toSyftLicenses(m []model.License) (p []pkg.License) {
|
|||||||
SPDXExpression: l.SPDXExpression,
|
SPDXExpression: l.SPDXExpression,
|
||||||
Type: l.Type,
|
Type: l.Type,
|
||||||
URLs: l.URLs,
|
URLs: l.URLs,
|
||||||
Locations: file.NewLocationSet(l.Locations...),
|
Locations: file.NewLocationSet(internal.NormalizeLocations(l.Locations)...),
|
||||||
Contents: l.Contents,
|
Contents: l.Contents,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -239,7 +239,7 @@ func toSyftRelationships(doc *model.Document, catalog *pkg.Collection, relations
|
|||||||
idMap[doc.Source.ID] = toSyftSource(doc.Source)
|
idMap[doc.Source.ID] = toSyftSource(doc.Source)
|
||||||
|
|
||||||
for _, f := range doc.Files {
|
for _, f := range doc.Files {
|
||||||
idMap[f.ID] = f.Location
|
idMap[f.ID] = internal.NormalizeCoordinates(f.Location)
|
||||||
}
|
}
|
||||||
|
|
||||||
var out []artifact.Relationship
|
var out []artifact.Relationship
|
||||||
@@ -346,7 +346,7 @@ func toSyftPackage(p model.Package, idAliases map[string]string) pkg.Package {
|
|||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
Version: p.Version,
|
Version: p.Version,
|
||||||
FoundBy: p.FoundBy,
|
FoundBy: p.FoundBy,
|
||||||
Locations: file.NewLocationSet(p.Locations...),
|
Locations: file.NewLocationSet(internal.NormalizeLocations(p.Locations)...),
|
||||||
Licenses: pkg.NewLicenseSet(toSyftLicenses(p.Licenses)...),
|
Licenses: pkg.NewLicenseSet(toSyftLicenses(p.Licenses)...),
|
||||||
Language: p.Language,
|
Language: p.Language,
|
||||||
Type: p.Type,
|
Type: p.Type,
|
||||||
|
|||||||
@@ -599,3 +599,35 @@ func Test_safeFileModeConvert(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_toSyftModel_fullPathIsNormalized(t *testing.T) {
|
||||||
|
plain := file.NewCoordinates("/app.jar", "")
|
||||||
|
nested := file.Coordinates{RealPath: "/app.war", FullPath: "/app.war!/lib/a.jar"}
|
||||||
|
decoded := []file.Coordinates{
|
||||||
|
{RealPath: "/app.jar", FullPath: "/app.jar"},
|
||||||
|
{RealPath: "/app.jar", FullPath: "/elsewhere!/app.jar"},
|
||||||
|
nested,
|
||||||
|
}
|
||||||
|
want := []file.Coordinates{plain, plain, nested}
|
||||||
|
|
||||||
|
var files []model.File
|
||||||
|
for _, c := range decoded {
|
||||||
|
files = append(files, model.File{ID: string(c.ID()), Location: c, Unknowns: []string{"x"}})
|
||||||
|
}
|
||||||
|
got := toSyftFiles(files)
|
||||||
|
for _, c := range want {
|
||||||
|
assert.Contains(t, got.Unknowns, c)
|
||||||
|
}
|
||||||
|
assert.Len(t, got.Unknowns, 2)
|
||||||
|
|
||||||
|
var locations []file.Location
|
||||||
|
for _, c := range decoded {
|
||||||
|
locations = append(locations, file.NewLocationFromCoordinates(c))
|
||||||
|
}
|
||||||
|
p := toSyftPackage(model.Package{PackageBasicData: model.PackageBasicData{Locations: locations}}, nil)
|
||||||
|
var gotCoordinates []file.Coordinates
|
||||||
|
for _, l := range p.Locations.ToSlice() {
|
||||||
|
gotCoordinates = append(gotCoordinates, l.Coordinates)
|
||||||
|
}
|
||||||
|
assert.ElementsMatch(t, []file.Coordinates{plain, plain, nested}, gotCoordinates)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user