From 3e99c4d7d88d625da6d469d62ed645aaffd82924 Mon Sep 17 00:00:00 2001 From: Keith Zantow Date: Thu, 3 Nov 2022 10:17:15 -0400 Subject: [PATCH] Fix: Include version information in binary cataloger CPEs (#1310) --- syft/pkg/cataloger/generic/classifier.go | 53 ++++++---- syft/pkg/cataloger/generic/classifier_test.go | 96 +++++++++++++++++++ .../generic/test-fixtures/version.txt | 1 + 3 files changed, 130 insertions(+), 20 deletions(-) create mode 100644 syft/pkg/cataloger/generic/classifier_test.go create mode 100644 syft/pkg/cataloger/generic/test-fixtures/version.txt diff --git a/syft/pkg/cataloger/generic/classifier.go b/syft/pkg/cataloger/generic/classifier.go index 8a557fff805..ae960afea7f 100644 --- a/syft/pkg/cataloger/generic/classifier.go +++ b/syft/pkg/cataloger/generic/classifier.go @@ -7,6 +7,7 @@ import ( "regexp" "github.com/anchore/syft/internal" + "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" @@ -22,10 +23,10 @@ type Classifier struct { // source location. If any of the patterns match, the file will be considered a candidate for parsing. // If no patterns are provided, the reader is automatically considered a candidate. FilepathPatterns []*regexp.Regexp - // EvidencePattern is a list of regular expressions that will be used to match against the file contents of a + // EvidencePatterns is a list of regular expressions that will be used to match against the file contents of a // given file in the source location. If any of the patterns match, the file will be considered a candidate for parsing. EvidencePatterns []*regexp.Regexp - // CPE is the CPE we want to match against + // CPEs are the specific CPEs we want to include for this binary with updated version information CPEs []pkg.CPE } @@ -45,29 +46,41 @@ func (c Classifier) Examine(reader source.LocationReadCloser) (p *pkg.Package, r } var classifiedPackage *pkg.Package - for _, patternTemplate := range c.EvidencePatterns { - if !patternTemplate.Match(contents) { + for _, evidencePattern := range c.EvidencePatterns { + if !evidencePattern.Match(contents) { continue } - matchMetadata := internal.MatchNamedCaptureGroups(patternTemplate, string(contents)) - if classifiedPackage == nil { - classifiedPackage = &pkg.Package{ - Name: path.Base(reader.VirtualPath), - Version: matchMetadata["version"], - Language: pkg.Binary, - Locations: source.NewLocationSet(reader.Location), - Type: pkg.BinaryPkg, - CPEs: c.CPEs, - MetadataType: pkg.BinaryMetadataType, - Metadata: pkg.BinaryMetadata{ - Classifier: c.Package, - RealPath: reader.RealPath, - VirtualPath: reader.VirtualPath, - }, + matchMetadata := internal.MatchNamedCaptureGroups(evidencePattern, string(contents)) + version, ok := matchMetadata["version"] + if !ok { + log.Debugf("no version found in binary from pattern %v", evidencePattern) + continue + } + + var cpes []pkg.CPE + for _, cpe := range c.CPEs { + cpe.Version = version + if err == nil { + cpes = append(cpes, cpe) } - break } + + classifiedPackage = &pkg.Package{ + Name: path.Base(reader.VirtualPath), + Version: version, + Language: pkg.Binary, + Locations: source.NewLocationSet(reader.Location), + Type: pkg.BinaryPkg, + CPEs: cpes, + MetadataType: pkg.BinaryMetadataType, + Metadata: pkg.BinaryMetadata{ + Classifier: c.Package, + RealPath: reader.RealPath, + VirtualPath: reader.VirtualPath, + }, + } + break } return classifiedPackage, nil, nil } diff --git a/syft/pkg/cataloger/generic/classifier_test.go b/syft/pkg/cataloger/generic/classifier_test.go new file mode 100644 index 00000000000..77242e4fa5b --- /dev/null +++ b/syft/pkg/cataloger/generic/classifier_test.go @@ -0,0 +1,96 @@ +package generic + +import ( + "regexp" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/source" +) + +func Test_ClassifierCPEs(t *testing.T) { + tests := []struct { + name string + fixture string + classifier Classifier + cpes []string + }{ + { + name: "no CPEs", + fixture: "test-fixtures/version.txt", + classifier: Classifier{ + Package: "some-app", + FilepathPatterns: []*regexp.Regexp{ + regexp.MustCompile(".*/version.txt"), + }, + EvidencePatterns: []*regexp.Regexp{ + regexp.MustCompile(`(?m)my-verison:(?P[0-9.]+)`), + }, + CPEs: []pkg.CPE{}, + }, + cpes: nil, + }, + { + name: "one CPE", + fixture: "test-fixtures/version.txt", + classifier: Classifier{ + Package: "some-app", + FilepathPatterns: []*regexp.Regexp{ + regexp.MustCompile(".*/version.txt"), + }, + EvidencePatterns: []*regexp.Regexp{ + regexp.MustCompile(`(?m)my-verison:(?P[0-9.]+)`), + }, + CPEs: []pkg.CPE{ + pkg.MustCPE("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*"), + }, + }, + cpes: []string{ + "cpe:2.3:a:some:app:1.8:*:*:*:*:*:*:*", + }, + }, + { + name: "multiple CPEs", + fixture: "test-fixtures/version.txt", + classifier: Classifier{ + Package: "some-app", + FilepathPatterns: []*regexp.Regexp{ + regexp.MustCompile(".*/version.txt"), + }, + EvidencePatterns: []*regexp.Regexp{ + regexp.MustCompile(`(?m)my-verison:(?P[0-9.]+)`), + }, + CPEs: []pkg.CPE{ + pkg.MustCPE("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*"), + pkg.MustCPE("cpe:2.3:a:some:apps:*:*:*:*:*:*:*:*"), + }, + }, + cpes: []string{ + "cpe:2.3:a:some:app:1.8:*:*:*:*:*:*:*", + "cpe:2.3:a:some:apps:1.8:*:*:*:*:*:*:*", + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + resolver := source.NewMockResolverForPaths(test.fixture) + locations, err := resolver.FilesByPath(test.fixture) + require.NoError(t, err) + require.Len(t, locations, 1) + location := locations[0] + readCloser, err := resolver.FileContentsByLocation(location) + require.NoError(t, err) + p, _, err := test.classifier.Examine(source.NewLocationReadCloser(location, readCloser)) + require.NoError(t, err) + + var cpes []string + for _, c := range p.CPEs { + cpes = append(cpes, pkg.CPEString(c)) + } + require.Equal(t, test.cpes, cpes) + }) + } +} diff --git a/syft/pkg/cataloger/generic/test-fixtures/version.txt b/syft/pkg/cataloger/generic/test-fixtures/version.txt new file mode 100644 index 00000000000..5baa206f049 --- /dev/null +++ b/syft/pkg/cataloger/generic/test-fixtures/version.txt @@ -0,0 +1 @@ +my-verison:1.8 \ No newline at end of file