From a16a4ad6c993aaaa1ec502667c215b0e8c8e51ff Mon Sep 17 00:00:00 2001 From: Laurent Goderre Date: Fri, 5 Jan 2024 15:32:07 -0500 Subject: [PATCH] Add ability to extend the binaries cataloguers (#2469) * Add ability to extend the binaries cataloguers Signed-off-by: Laurent Goderre * restrict binary classifier package attributes Signed-off-by: Alex Goodman --------- Signed-off-by: Laurent Goderre Signed-off-by: Alex Goodman Co-authored-by: Alex Goodman --- cmd/syft/cli/options/catalog.go | 2 + syft/pkg/cataloger/binary/cataloger.go | 24 +- syft/pkg/cataloger/binary/cataloger_test.go | 131 +++- syft/pkg/cataloger/binary/classifier.go | 36 +- syft/pkg/cataloger/binary/classifier_test.go | 14 +- .../cataloger/binary/default_classifiers.go | 681 +++++++++--------- syft/pkg/cataloger/binary/package.go | 13 +- .../classifiers/positive/custom/foo | 3 + syft/pkg/cataloger/cataloger.go | 6 +- syft/pkg/cataloger/config.go | 3 + 10 files changed, 523 insertions(+), 390 deletions(-) create mode 100644 syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/custom/foo diff --git a/cmd/syft/cli/options/catalog.go b/cmd/syft/cli/options/catalog.go index dd3999f1f..5f9d06c77 100644 --- a/cmd/syft/cli/options/catalog.go +++ b/cmd/syft/cli/options/catalog.go @@ -14,6 +14,7 @@ import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/cataloging" "github.com/anchore/syft/syft/pkg/cataloger" + binaryCataloger "github.com/anchore/syft/syft/pkg/cataloger/binary" golangCataloger "github.com/anchore/syft/syft/pkg/cataloger/golang" javaCataloger "github.com/anchore/syft/syft/pkg/cataloger/java" javascriptCataloger "github.com/anchore/syft/syft/pkg/cataloger/javascript" @@ -150,6 +151,7 @@ func (cfg Catalog) ToCatalogerConfig() cataloger.Config { Javascript: javascriptCataloger.DefaultCatalogerConfig(). WithSearchRemoteLicenses(cfg.Javascript.SearchRemoteLicenses). WithNpmBaseURL(cfg.Javascript.NpmBaseURL), + Binary: binaryCataloger.DefaultCatalogerConfig(), Python: pythonCataloger.CatalogerConfig{ GuessUnpinnedRequirements: cfg.Python.GuessUnpinnedRequirements, }, diff --git a/syft/pkg/cataloger/binary/cataloger.go b/syft/pkg/cataloger/binary/cataloger.go index 24ee92ffb..8219a171d 100644 --- a/syft/pkg/cataloger/binary/cataloger.go +++ b/syft/pkg/cataloger/binary/cataloger.go @@ -12,8 +12,20 @@ import ( const catalogerName = "binary-cataloger" -func NewCataloger() pkg.Cataloger { - return &Cataloger{} +type CatalogerConfig struct { + Classifiers []Classifier +} + +func DefaultCatalogerConfig() CatalogerConfig { + return CatalogerConfig{ + Classifiers: DefaultClassifiers(), + } +} + +func NewCataloger(cfg CatalogerConfig) pkg.Cataloger { + return &Cataloger{ + classifiers: cfg.Classifiers, + } } // Cataloger is the cataloger responsible for surfacing evidence of a very limited set of binary files, @@ -21,7 +33,9 @@ func NewCataloger() pkg.Cataloger { // binary, but rather the specific set that has been curated to be important, predominantly related to toolchain- // related runtimes like Python, Go, Java, or Node. Some exceptions can be made for widely-used binaries such // as busybox. -type Cataloger struct{} +type Cataloger struct { + classifiers []Classifier +} // Name returns a string that uniquely describes the Cataloger func (c Cataloger) Name() string { @@ -34,7 +48,7 @@ func (c Cataloger) Catalog(resolver file.Resolver) ([]pkg.Package, []artifact.Re var packages []pkg.Package var relationships []artifact.Relationship - for _, cls := range defaultClassifiers { + for _, cls := range c.classifiers { log.WithFields("classifier", cls.Class).Trace("cataloging binaries") newPkgs, err := catalog(resolver, cls) if err != nil { @@ -71,7 +85,7 @@ func mergePackages(target *pkg.Package, extra *pkg.Package) { target.Metadata = meta } -func catalog(resolver file.Resolver, cls classifier) (packages []pkg.Package, err error) { +func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, err error) { locations, err := resolver.FilesByGlob(cls.FileGlob) if err != nil { return nil, err diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index a953f9e22..6db9d278c 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -780,7 +780,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - c := NewCataloger() + c := NewCataloger(DefaultCatalogerConfig()) src, err := source.NewFromDirectoryPath(test.fixtureDir) require.NoError(t, err) @@ -819,7 +819,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - c := NewCataloger() + c := NewCataloger(DefaultCatalogerConfig()) img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureImage) src, err := source.NewFromStereoscopeImageObject(img, test.fixtureImage, nil) @@ -850,7 +850,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T) { } func TestClassifierCataloger_DefaultClassifiers_NegativeCases(t *testing.T) { - c := NewCataloger() + c := NewCataloger(DefaultCatalogerConfig()) src, err := source.NewFromDirectoryPath("test-fixtures/classifiers/negative") assert.NoError(t, err) @@ -863,6 +863,129 @@ func TestClassifierCataloger_DefaultClassifiers_NegativeCases(t *testing.T) { assert.Equal(t, 0, len(actualResults)) } +func Test_Cataloger_CustomClassifiers(t *testing.T) { + defaultClassifers := DefaultClassifiers() + + golangExpected := pkg.Package{ + Name: "go", + Version: "1.14", + PURL: "pkg:generic/go@1.14", + Locations: locations("go"), + Metadata: metadata("go-binary"), + } + customExpected := pkg.Package{ + Name: "foo", + Version: "1.2.3", + PURL: "pkg:generic/foo@1.2.3", + Locations: locations("foo"), + Metadata: metadata("foo-binary"), + } + fooClassifier := Classifier{ + Class: "foo-binary", + FileGlob: "**/foo", + EvidenceMatcher: FileContentsVersionMatcher( + `(?m)foobar\s(?P[0-9]+\.[0-9]+\.[0-9]+)`), + Package: "foo", + PURL: mustPURL("pkg:generic/foo@version"), + CPEs: singleCPE("cpe:2.3:a:foo:foo:*:*:*:*:*:*:*:*"), + } + + tests := []struct { + name string + config CatalogerConfig + fixtureDir string + expected *pkg.Package + }{ + { + name: "empty-negative", + config: CatalogerConfig{ + Classifiers: []Classifier{}, + }, + fixtureDir: "test-fixtures/classifiers/positive/go-1.14", + expected: nil, + }, + { + name: "default-positive", + config: CatalogerConfig{ + Classifiers: defaultClassifers, + }, + fixtureDir: "test-fixtures/classifiers/positive/go-1.14", + expected: &golangExpected, + }, + { + name: "nodefault-negative", + config: CatalogerConfig{ + Classifiers: []Classifier{fooClassifier}, + }, + fixtureDir: "test-fixtures/classifiers/positive/go-1.14", + expected: nil, + }, + { + name: "default-extended-positive", + config: CatalogerConfig{ + Classifiers: append( + append([]Classifier{}, defaultClassifers...), + fooClassifier, + ), + }, + fixtureDir: "test-fixtures/classifiers/positive/go-1.14", + expected: &golangExpected, + }, + { + name: "default-cutsom-negative", + config: CatalogerConfig{ + + Classifiers: append( + append([]Classifier{}, defaultClassifers...), + Classifier{ + Class: "foo-binary", + FileGlob: "**/foo", + EvidenceMatcher: FileContentsVersionMatcher(`(?m)not there`), + Package: "foo", + PURL: mustPURL("pkg:generic/foo@version"), + CPEs: singleCPE("cpe:2.3:a:foo:foo:*:*:*:*:*:*:*:*"), + }, + ), + }, + fixtureDir: "test-fixtures/classifiers/positive/custom", + expected: nil, + }, + { + name: "default-cutsom-positive", + config: CatalogerConfig{ + Classifiers: append( + append([]Classifier{}, defaultClassifers...), + fooClassifier, + ), + }, + fixtureDir: "test-fixtures/classifiers/positive/custom", + expected: &customExpected, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + c := NewCataloger(test.config) + + src, err := source.NewFromDirectoryPath(test.fixtureDir) + require.NoError(t, err) + + resolver, err := src.FileResolver(source.SquashedScope) + require.NoError(t, err) + + packages, _, err := c.Catalog(resolver) + require.NoError(t, err) + + if test.expected == nil { + assert.Equal(t, 0, len(packages)) + } else { + require.Len(t, packages, 1) + + assertPackagesAreEqual(t, *test.expected, packages[0]) + } + }) + } +} + func locations(locations ...string) file.LocationSet { var locs []file.Location for _, s := range locations { @@ -1019,7 +1142,7 @@ func (p *panicyResolver) FileMetadataByLocation(_ file.Location) (file.Metadata, var _ file.Resolver = (*panicyResolver)(nil) func Test_Cataloger_ResilientToErrors(t *testing.T) { - c := NewCataloger() + c := NewCataloger(DefaultCatalogerConfig()) resolver := &panicyResolver{} _, _, err := c.Catalog(resolver) diff --git a/syft/pkg/cataloger/binary/classifier.go b/syft/pkg/cataloger/binary/classifier.go index b06f38fe9..be818a3ad 100644 --- a/syft/pkg/cataloger/binary/classifier.go +++ b/syft/pkg/cataloger/binary/classifier.go @@ -20,11 +20,9 @@ import ( "github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader" ) -var emptyPURL = packageurl.PackageURL{} - -// classifier is a generic package classifier that can be used to match a package definition -// to a file that meets the given content criteria of the evidenceMatcher. -type classifier struct { +// Classifier is a generic package classifier that can be used to match a package definition +// to a file that meets the given content criteria of the EvidenceMatcher. +type Classifier struct { Class string // FileGlob is a selector to narrow down file inspection using the **/glob* syntax @@ -32,19 +30,13 @@ type classifier struct { // EvidenceMatcher is what will be used to match against the file in the source // location. If the matcher returns a package, the file will be considered a candidate. - EvidenceMatcher evidenceMatcher + EvidenceMatcher EvidenceMatcher // Information below is used to specify the Package information when returned // Package is the name to use for the package Package string - // Language is the language to classify this package as - Language pkg.Language - - // Type is the package type to use for the package - Type pkg.Type - // PURL is the Package URL to use when generating a package PURL packageurl.PackageURL @@ -52,11 +44,11 @@ type classifier struct { CPEs []cpe.CPE } -// evidenceMatcher is a function called to catalog Packages that match some sort of evidence -type evidenceMatcher func(resolver file.Resolver, classifier classifier, location file.Location) ([]pkg.Package, error) +// EvidenceMatcher is a function called to catalog Packages that match some sort of evidence +type EvidenceMatcher func(resolver file.Resolver, classifier Classifier, location file.Location) ([]pkg.Package, error) -func evidenceMatchers(matchers ...evidenceMatcher) evidenceMatcher { - return func(resolver file.Resolver, classifier classifier, location file.Location) ([]pkg.Package, error) { +func evidenceMatchers(matchers ...EvidenceMatcher) EvidenceMatcher { + return func(resolver file.Resolver, classifier Classifier, location file.Location) ([]pkg.Package, error) { for _, matcher := range matchers { match, err := matcher(resolver, classifier, location) if err != nil { @@ -70,9 +62,9 @@ func evidenceMatchers(matchers ...evidenceMatcher) evidenceMatcher { } } -func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate string) evidenceMatcher { +func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate string) EvidenceMatcher { pat := regexp.MustCompile(fileNamePattern) - return func(resolver file.Resolver, classifier classifier, location file.Location) ([]pkg.Package, error) { + return func(resolver file.Resolver, classifier Classifier, location file.Location) ([]pkg.Package, error) { if !pat.MatchString(location.RealPath) { return nil, nil } @@ -116,9 +108,9 @@ func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate stri } } -func fileContentsVersionMatcher(pattern string) evidenceMatcher { +func FileContentsVersionMatcher(pattern string) EvidenceMatcher { pat := regexp.MustCompile(pattern) - return func(resolver file.Resolver, classifier classifier, location file.Location) ([]pkg.Package, error) { + return func(resolver file.Resolver, classifier Classifier, location file.Location) ([]pkg.Package, error) { contents, err := getContents(resolver, location) if err != nil { return nil, fmt.Errorf("unable to get read contents for file: %w", err) @@ -136,9 +128,9 @@ func fileContentsVersionMatcher(pattern string) evidenceMatcher { } //nolint:gocognit -func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher evidenceMatcher) evidenceMatcher { +func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher EvidenceMatcher) EvidenceMatcher { pat := regexp.MustCompile(sharedLibraryPattern) - return func(resolver file.Resolver, classifier classifier, location file.Location) (packages []pkg.Package, _ error) { + return func(resolver file.Resolver, classifier Classifier, location file.Location) (packages []pkg.Package, _ error) { libs, err := sharedLibraries(resolver, location) if err != nil { return nil, err diff --git a/syft/pkg/cataloger/binary/classifier_test.go b/syft/pkg/cataloger/binary/classifier_test.go index fbf88c3b1..9fbdf73fe 100644 --- a/syft/pkg/cataloger/binary/classifier_test.go +++ b/syft/pkg/cataloger/binary/classifier_test.go @@ -13,16 +13,16 @@ func Test_ClassifierCPEs(t *testing.T) { tests := []struct { name string fixture string - classifier classifier + classifier Classifier cpes []string }{ { name: "no CPEs", fixture: "test-fixtures/version.txt", - classifier: classifier{ + classifier: Classifier{ Package: "some-app", FileGlob: "**/version.txt", - EvidenceMatcher: fileContentsVersionMatcher(`(?m)my-verison:(?P[0-9.]+)`), + EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P[0-9.]+)`), CPEs: []cpe.CPE{}, }, cpes: nil, @@ -30,10 +30,10 @@ func Test_ClassifierCPEs(t *testing.T) { { name: "one CPE", fixture: "test-fixtures/version.txt", - classifier: classifier{ + classifier: Classifier{ Package: "some-app", FileGlob: "**/version.txt", - EvidenceMatcher: fileContentsVersionMatcher(`(?m)my-verison:(?P[0-9.]+)`), + EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P[0-9.]+)`), CPEs: []cpe.CPE{ cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*"), }, @@ -45,10 +45,10 @@ func Test_ClassifierCPEs(t *testing.T) { { name: "multiple CPEs", fixture: "test-fixtures/version.txt", - classifier: classifier{ + classifier: Classifier{ Package: "some-app", FileGlob: "**/version.txt", - EvidenceMatcher: fileContentsVersionMatcher(`(?m)my-verison:(?P[0-9.]+)`), + EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P[0-9.]+)`), CPEs: []cpe.CPE{ cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*"), cpe.Must("cpe:2.3:a:some:apps:*:*:*:*:*:*:*:*"), diff --git a/syft/pkg/cataloger/binary/default_classifiers.go b/syft/pkg/cataloger/binary/default_classifiers.go index d23444dce..00c997c9e 100644 --- a/syft/pkg/cataloger/binary/default_classifiers.go +++ b/syft/pkg/cataloger/binary/default_classifiers.go @@ -2,350 +2,351 @@ package binary import ( "github.com/anchore/syft/syft/cpe" - "github.com/anchore/syft/syft/pkg" ) -var defaultClassifiers = []classifier{ - { - Class: "python-binary", - FileGlob: "**/python*", - EvidenceMatcher: evidenceMatchers( - // try to find version information from libpython shared libraries - sharedLibraryLookup( - `^libpython[0-9]+(?:\.[0-9]+)+[a-z]?\.so.*$`, - libpythonMatcher), - // check for version information in the binary - fileNameTemplateVersionMatcher( - `(?:.*/|^)python(?P[0-9]+(?:\.[0-9]+)+)$`, - pythonVersionTemplate), - ), - Package: "python", - PURL: mustPURL("pkg:generic/python@version"), - CPEs: []cpe.CPE{ - cpe.Must("cpe:2.3:a:python_software_foundation:python:*:*:*:*:*:*:*:*"), - cpe.Must("cpe:2.3:a:python:python:*:*:*:*:*:*:*:*"), +//nolint:funlen +func DefaultClassifiers() []Classifier { + return []Classifier{ + { + Class: "python-binary", + FileGlob: "**/python*", + EvidenceMatcher: evidenceMatchers( + // try to find version information from libpython shared libraries + sharedLibraryLookup( + `^libpython[0-9]+(?:\.[0-9]+)+[a-z]?\.so.*$`, + libpythonMatcher), + // check for version information in the binary + fileNameTemplateVersionMatcher( + `(?:.*/|^)python(?P[0-9]+(?:\.[0-9]+)+)$`, + pythonVersionTemplate), + ), + Package: "python", + PURL: mustPURL("pkg:generic/python@version"), + CPEs: []cpe.CPE{ + cpe.Must("cpe:2.3:a:python_software_foundation:python:*:*:*:*:*:*:*:*"), + cpe.Must("cpe:2.3:a:python:python:*:*:*:*:*:*:*:*"), + }, }, - }, - { - Class: "python-binary-lib", - FileGlob: "**/libpython*.so*", - EvidenceMatcher: libpythonMatcher, - Package: "python", - PURL: mustPURL("pkg:generic/python@version"), - CPEs: []cpe.CPE{ - cpe.Must("cpe:2.3:a:python_software_foundation:python:*:*:*:*:*:*:*:*"), - cpe.Must("cpe:2.3:a:python:python:*:*:*:*:*:*:*:*"), + { + Class: "python-binary-lib", + FileGlob: "**/libpython*.so*", + EvidenceMatcher: libpythonMatcher, + Package: "python", + PURL: mustPURL("pkg:generic/python@version"), + CPEs: []cpe.CPE{ + cpe.Must("cpe:2.3:a:python_software_foundation:python:*:*:*:*:*:*:*:*"), + cpe.Must("cpe:2.3:a:python:python:*:*:*:*:*:*:*:*"), + }, }, - }, - { - Class: "go-binary", - FileGlob: "**/go", - EvidenceMatcher: fileContentsVersionMatcher( - `(?m)go(?P[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)\x00`), - Package: "go", - PURL: mustPURL("pkg:generic/go@version"), - CPEs: singleCPE("cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*"), - }, - { - Class: "julia-binary", - FileGlob: "**/libjulia-internal.so", - EvidenceMatcher: fileContentsVersionMatcher( - `(?m)__init__\x00(?P[0-9]+\.[0-9]+\.[0-9]+)\x00verify`), - Package: "julia", - PURL: mustPURL("pkg:generic/julia@version"), - CPEs: singleCPE("cpe:2.3:a:julialang:julia:*:*:*:*:*:*:*:*"), - }, - { - Class: "helm", - FileGlob: "**/helm", - EvidenceMatcher: fileContentsVersionMatcher( - `(?m)\x00v(?P[0-9]+\.[0-9]+\.[0-9]+)\x00`), - Package: "helm", - PURL: mustPURL("pkg:golang/helm.sh/helm@version"), - CPEs: singleCPE("cpe:2.3:a:helm:helm:*:*:*:*:*:*:*"), - }, - { - Class: "redis-binary", - FileGlob: "**/redis-server", - EvidenceMatcher: evidenceMatchers( - fileContentsVersionMatcher(`(?s)payload %5.*?(?P\d.\d\.\d\d*)[a-z0-9]{12,15}-[0-9]{19}`), - fileContentsVersionMatcher(`(?s)\x00(?P\d.\d\.\d\d*)[a-z0-9]{12}-[0-9]{19}\x00.*?payload %5`), - ), - Package: "redis", - PURL: mustPURL("pkg:generic/redis@version"), - CPEs: singleCPE("cpe:2.3:a:redislabs:redis:*:*:*:*:*:*:*:*"), - }, - { - Class: "java-binary-openjdk", - FileGlob: "**/java", - EvidenceMatcher: fileContentsVersionMatcher( - // [NUL]openjdk[NUL]java[NUL]0.0[NUL]11.0.17+8-LTS[NUL] - // [NUL]openjdk[NUL]java[NUL]1.8[NUL]1.8.0_352-b08[NUL] - `(?m)\x00openjdk\x00java\x00(?P[0-9]+[.0-9]*)\x00(?P[0-9]+[^\x00]+)\x00`), - Package: "java", - PURL: mustPURL("pkg:generic/java@version"), - // TODO the updates might need to be part of the CPE, like: 1.8.0:update152 - CPEs: singleCPE("cpe:2.3:a:oracle:openjdk:*:*:*:*:*:*:*:*"), - }, - { - Class: "java-binary-ibm", - FileGlob: "**/java", - EvidenceMatcher: fileContentsVersionMatcher( - // [NUL]java[NUL]1.8[NUL][NUL][NUL][NUL]1.8.0-foreman_2022_09_22_15_30-b00[NUL] - `(?m)\x00java\x00(?P[0-9]+[.0-9]+)\x00{4}(?P[0-9]+[-._a-zA-Z0-9]+)\x00`), - Package: "java", - PURL: mustPURL("pkg:generic/java@version"), - CPEs: singleCPE("cpe:2.3:a:ibm:java:*:*:*:*:*:*:*:*"), - }, - { - Class: "java-binary-oracle", - FileGlob: "**/java", - EvidenceMatcher: fileContentsVersionMatcher( - // [NUL]19.0.1+10-21[NUL] - `(?m)\x00(?P[0-9]+[.0-9]+[+][-0-9]+)\x00`), - Package: "java", - PURL: mustPURL("pkg:generic/java@version"), - CPEs: singleCPE("cpe:2.3:a:oracle:jre:*:*:*:*:*:*:*:*"), - }, - { - Class: "nodejs-binary", - FileGlob: "**/node", - EvidenceMatcher: fileContentsVersionMatcher( - `(?m)node\.js\/v(?P[0-9]+\.[0-9]+\.[0-9]+)`), - Package: "node", - Language: pkg.JavaScript, - PURL: mustPURL("pkg:generic/node@version"), - CPEs: singleCPE("cpe:2.3:a:nodejs:node.js:*:*:*:*:*:*:*:*"), - }, - { - Class: "go-binary-hint", - FileGlob: "**/VERSION", - EvidenceMatcher: fileContentsVersionMatcher( - `(?m)go(?P[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)`), - Package: "go", - PURL: mustPURL("pkg:generic/go@version"), - }, - { - Class: "busybox-binary", - FileGlob: "**/busybox", - EvidenceMatcher: fileContentsVersionMatcher( - `(?m)BusyBox\s+v(?P[0-9]+\.[0-9]+\.[0-9]+)`), - Package: "busybox", - PURL: mustPURL("pkg:generic/busybox@version"), - CPEs: singleCPE("cpe:2.3:a:busybox:busybox:*:*:*:*:*:*:*:*"), - }, - { - Class: "haproxy-binary", - FileGlob: "**/haproxy", - EvidenceMatcher: evidenceMatchers( - fileContentsVersionMatcher(`(?m)HA-Proxy version (?P[0-9]+\.[0-9]+\.[0-9]+)`), - fileContentsVersionMatcher(`(?m)(?P[0-9]+\.[0-9]+\.[0-9]+)-[0-9a-zA-Z]{7}.+HAProxy version`), - ), - Package: "haproxy", - PURL: mustPURL("pkg:generic/haproxy@version"), - CPEs: singleCPE("cpe:2.3:a:haproxy:haproxy:*:*:*:*:*:*:*:*"), - }, - { - Class: "perl-binary", - FileGlob: "**/perl", - EvidenceMatcher: fileContentsVersionMatcher( - `(?m)\/usr\/local\/lib\/perl\d\/(?P[0-9]+\.[0-9]+\.[0-9]+)`), - Package: "perl", - PURL: mustPURL("pkg:generic/perl@version"), - CPEs: singleCPE("cpe:2.3:a:perl:perl:*:*:*:*:*:*:*:*"), - }, - { - Class: "php-cli-binary", - FileGlob: "**/php*", - EvidenceMatcher: fileNameTemplateVersionMatcher( - `(.*/|^)php[0-9]*$`, - `(?m)X-Powered-By: PHP\/(?P[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`), - Package: "php-cli", - PURL: mustPURL("pkg:generic/php-cli@version"), - CPEs: singleCPE("cpe:2.3:a:php:php:*:*:*:*:*:*:*:*"), - }, - { - Class: "php-fpm-binary", - FileGlob: "**/php-fpm*", - EvidenceMatcher: fileContentsVersionMatcher( - `(?m)X-Powered-By: PHP\/(?P[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`), - Package: "php-fpm", - PURL: mustPURL("pkg:generic/php-fpm@version"), - CPEs: singleCPE("cpe:2.3:a:php:php:*:*:*:*:*:*:*:*"), - }, - { - Class: "php-apache-binary", - FileGlob: "**/libphp*.so", - EvidenceMatcher: fileContentsVersionMatcher( - `(?m)X-Powered-By: PHP\/(?P[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`), - Package: "libphp", - PURL: mustPURL("pkg:generic/php@version"), - CPEs: singleCPE("cpe:2.3:a:php:php:*:*:*:*:*:*:*:*"), - }, - { - Class: "php-composer-binary", - FileGlob: "**/composer*", - EvidenceMatcher: fileContentsVersionMatcher( - `(?m)'pretty_version'\s*=>\s*'(?P[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)'`), - Package: "composer", - PURL: mustPURL("pkg:generic/composer@version"), - CPEs: singleCPE("cpe:2.3:a:getcomposer:composer:*:*:*:*:*:*:*:*"), - }, - { - Class: "httpd-binary", - FileGlob: "**/httpd", - EvidenceMatcher: fileContentsVersionMatcher( - `(?m)Apache\/(?P[0-9]+\.[0-9]+\.[0-9]+)`), - Package: "httpd", - PURL: mustPURL("pkg:generic/httpd@version"), - CPEs: singleCPE("cpe:2.3:a:apache:http_server:*:*:*:*:*:*:*:*"), - }, - { - Class: "memcached-binary", - FileGlob: "**/memcached", - EvidenceMatcher: fileContentsVersionMatcher( - `(?m)memcached\s(?P[0-9]+\.[0-9]+\.[0-9]+)`), - Package: "memcached", - PURL: mustPURL("pkg:generic/memcached@version"), - }, - { - Class: "traefik-binary", - FileGlob: "**/traefik", - EvidenceMatcher: fileContentsVersionMatcher( - // [NUL]v1.7.34[NUL] - // [NUL]2.9.6[NUL] - `(?m)\x00v?(?P[0-9]+\.[0-9]+\.[0-9]+(-alpha[0-9]|-beta[0-9]|-rc[0-9])?)\x00`), - Package: "traefik", - PURL: mustPURL("pkg:generic/traefik@version"), - }, - { - Class: "postgresql-binary", - FileGlob: "**/postgres", - EvidenceMatcher: fileContentsVersionMatcher( - // [NUL]PostgreSQL 15beta4 - // [NUL]PostgreSQL 15.1 - // [NUL]PostgreSQL 9.6.24 - // ?PostgreSQL 9.5alpha1 - `(?m)(\x00|\?)PostgreSQL (?P[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`), - Package: "postgresql", - PURL: mustPURL("pkg:generic/postgresql@version"), - }, - { - Class: "mysql-binary", - FileGlob: "**/mysql", - EvidenceMatcher: fileContentsVersionMatcher( - // ../../mysql-8.0.34 - // /mysql-5.6.51/bld/client - `(?m).*/mysql-(?P[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`), - Package: "mysql", - PURL: mustPURL("pkg:generic/mysql@version"), - CPEs: singleCPE("cpe:2.3:a:oracle:mysql:*:*:*:*:*:*:*:*"), - }, - { - Class: "mariadb-binary", - FileGlob: "**/mariadb", - EvidenceMatcher: fileContentsVersionMatcher( - // 10.6.15-MariaDB - `(?m)(?P[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)-MariaDB`), - Package: "mariadb", - PURL: mustPURL("pkg:generic/mariadb@version"), - }, - { - Class: "rust-standard-library-linux", - FileGlob: "**/libstd-????????????????.so", - EvidenceMatcher: fileContentsVersionMatcher( - // clang LLVM (rustc version 1.48.0 (7eac88abb 2020-11-16)) - `(?m)(\x00)clang LLVM \(rustc version (?P[0-9]+(\.[0-9]+)?(\.[0-9]+)) \(\w+ \d{4}\-\d{2}\-\d{2}\)`), - Package: "rust", - PURL: mustPURL("pkg:generic/rust@version"), - CPEs: singleCPE("cpe:2.3:a:rust-lang:rust:*:*:*:*:*:*:*:*"), - }, - { - Class: "rust-standard-library-macos", - FileGlob: "**/libstd-????????????????.dylib", - EvidenceMatcher: fileContentsVersionMatcher( - // c 1.48.0 (7eac88abb 2020-11-16) - `(?m)c (?P[0-9]+(\.[0-9]+)?(\.[0-9]+)) \(\w+ \d{4}\-\d{2}\-\d{2}\)`), - Package: "rust", - PURL: mustPURL("pkg:generic/rust@version"), - CPEs: singleCPE("cpe:2.3:a:rust-lang:rust:*:*:*:*:*:*:*:*"), - }, - { - Class: "ruby-binary", - FileGlob: "**/ruby", - EvidenceMatcher: evidenceMatchers( - rubyMatcher, - sharedLibraryLookup( - // try to find version information from libruby shared libraries - `^libruby\.so.*$`, - rubyMatcher), - ), - Package: "ruby", - PURL: mustPURL("pkg:generic/ruby@version"), - CPEs: singleCPE("cpe:2.3:a:ruby-lang:ruby:*:*:*:*:*:*:*:*"), - }, - { - Class: "erlang-binary", - FileGlob: "**/erlexec", - EvidenceMatcher: fileContentsVersionMatcher( - // [NUL]/usr/local/src/otp-25.3.2.7/erts/ - `(?m)\\x00/usr/local/src/otp-(?P[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+?)/erts/`, - ), - Package: "erlang", - PURL: mustPURL("pkg:generic/erlang@version"), - CPEs: singleCPE("cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"), - }, - { - Class: "consul-binary", - FileGlob: "**/consul", - EvidenceMatcher: fileContentsVersionMatcher( - // NOTE: This is brittle and may not work for past or future versions - `CONSUL_VERSION: (?P\d+\.\d+\.\d+)`, - ), - Package: "consul", - PURL: mustPURL("pkg:golang/github.com/hashicorp/consul@version"), - CPEs: singleCPE("cpe:2.3:a:hashicorp:consul:*:*:*:*:*:*:*:*"), - }, - { - Class: "nginx-binary", - FileGlob: "**/nginx", - EvidenceMatcher: fileContentsVersionMatcher( - // [NUL]nginx version: nginx/1.25.1 - fetches '1.25.1' - // [NUL]nginx version: openresty/1.21.4.1 - fetches '1.21.4' as this is the nginx version part - `(?m)(\x00|\?)nginx version: [^\/]+\/(?P[0-9]+\.[0-9]+\.[0-9]+(?:\+\d+)?(?:-\d+)?)`, - ), - Package: "nginx", - PURL: mustPURL("pkg:generic/nginx@version"), - CPEs: []cpe.CPE{ - cpe.Must("cpe:2.3:a:f5:nginx:*:*:*:*:*:*:*:*"), - cpe.Must("cpe:2.3:a:nginx:nginx:*:*:*:*:*:*:*:*"), + { + Class: "go-binary", + FileGlob: "**/go", + EvidenceMatcher: FileContentsVersionMatcher( + `(?m)go(?P[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)\x00`), + Package: "go", + PURL: mustPURL("pkg:generic/go@version"), + CPEs: singleCPE("cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*"), }, - }, - { - Class: "bash-binary", - FileGlob: "**/bash", - EvidenceMatcher: fileContentsVersionMatcher( - // @(#)Bash version 5.2.15(1) release GNU - // @(#)Bash version 5.2.0(1) alpha GNU - // @(#)Bash version 5.2.0(1) beta GNU - // @(#)Bash version 5.2.0(1) rc4 GNU - `(?m)@\(#\)Bash version (?P[0-9]+\.[0-9]+\.[0-9]+)\([0-9]\) [a-z0-9]+ GNU`, - ), - Package: "bash", - PURL: mustPURL("pkg:generic/bash@version"), - CPEs: singleCPE("cpe:2.3:a:gnu:bash:*:*:*:*:*:*:*:*"), - }, - { - Class: "openssl-binary", - FileGlob: "**/openssl", - EvidenceMatcher: fileContentsVersionMatcher( - // [NUL]OpenSSL 3.1.4' - `\x00OpenSSL (?P[0-9]+\.[0-9]+\.[0-9]+(-alpha[0-9]|-beta[0-9]|-rc[0-9])?)`, - ), - Package: "openssl", - PURL: mustPURL("pkg:generic/openssl@version"), - CPEs: singleCPE("cpe:2.3:a:openssl:openssl:*:*:*:*:*:*:*:*"), - }, + { + Class: "julia-binary", + FileGlob: "**/libjulia-internal.so", + EvidenceMatcher: FileContentsVersionMatcher( + `(?m)__init__\x00(?P[0-9]+\.[0-9]+\.[0-9]+)\x00verify`), + Package: "julia", + PURL: mustPURL("pkg:generic/julia@version"), + CPEs: singleCPE("cpe:2.3:a:julialang:julia:*:*:*:*:*:*:*:*"), + }, + { + Class: "helm", + FileGlob: "**/helm", + EvidenceMatcher: FileContentsVersionMatcher( + `(?m)\x00v(?P[0-9]+\.[0-9]+\.[0-9]+)\x00`), + Package: "helm", + PURL: mustPURL("pkg:golang/helm.sh/helm@version"), + CPEs: singleCPE("cpe:2.3:a:helm:helm:*:*:*:*:*:*:*"), + }, + { + Class: "redis-binary", + FileGlob: "**/redis-server", + EvidenceMatcher: evidenceMatchers( + FileContentsVersionMatcher(`(?s)payload %5.*?(?P\d.\d\.\d\d*)[a-z0-9]{12,15}-[0-9]{19}`), + FileContentsVersionMatcher(`(?s)\x00(?P\d.\d\.\d\d*)[a-z0-9]{12}-[0-9]{19}\x00.*?payload %5`), + ), + Package: "redis", + PURL: mustPURL("pkg:generic/redis@version"), + CPEs: singleCPE("cpe:2.3:a:redislabs:redis:*:*:*:*:*:*:*:*"), + }, + { + Class: "java-binary-openjdk", + FileGlob: "**/java", + EvidenceMatcher: FileContentsVersionMatcher( + // [NUL]openjdk[NUL]java[NUL]0.0[NUL]11.0.17+8-LTS[NUL] + // [NUL]openjdk[NUL]java[NUL]1.8[NUL]1.8.0_352-b08[NUL] + `(?m)\x00openjdk\x00java\x00(?P[0-9]+[.0-9]*)\x00(?P[0-9]+[^\x00]+)\x00`), + Package: "java", + PURL: mustPURL("pkg:generic/java@version"), + // TODO the updates might need to be part of the CPE, like: 1.8.0:update152 + CPEs: singleCPE("cpe:2.3:a:oracle:openjdk:*:*:*:*:*:*:*:*"), + }, + { + Class: "java-binary-ibm", + FileGlob: "**/java", + EvidenceMatcher: FileContentsVersionMatcher( + // [NUL]java[NUL]1.8[NUL][NUL][NUL][NUL]1.8.0-foreman_2022_09_22_15_30-b00[NUL] + `(?m)\x00java\x00(?P[0-9]+[.0-9]+)\x00{4}(?P[0-9]+[-._a-zA-Z0-9]+)\x00`), + Package: "java", + PURL: mustPURL("pkg:generic/java@version"), + CPEs: singleCPE("cpe:2.3:a:ibm:java:*:*:*:*:*:*:*:*"), + }, + { + Class: "java-binary-oracle", + FileGlob: "**/java", + EvidenceMatcher: FileContentsVersionMatcher( + // [NUL]19.0.1+10-21[NUL] + `(?m)\x00(?P[0-9]+[.0-9]+[+][-0-9]+)\x00`), + Package: "java", + PURL: mustPURL("pkg:generic/java@version"), + CPEs: singleCPE("cpe:2.3:a:oracle:jre:*:*:*:*:*:*:*:*"), + }, + { + Class: "nodejs-binary", + FileGlob: "**/node", + EvidenceMatcher: FileContentsVersionMatcher( + `(?m)node\.js\/v(?P[0-9]+\.[0-9]+\.[0-9]+)`), + Package: "node", + PURL: mustPURL("pkg:generic/node@version"), + CPEs: singleCPE("cpe:2.3:a:nodejs:node.js:*:*:*:*:*:*:*:*"), + }, + { + Class: "go-binary-hint", + FileGlob: "**/VERSION", + EvidenceMatcher: FileContentsVersionMatcher( + `(?m)go(?P[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)`), + Package: "go", + PURL: mustPURL("pkg:generic/go@version"), + }, + { + Class: "busybox-binary", + FileGlob: "**/busybox", + EvidenceMatcher: FileContentsVersionMatcher( + `(?m)BusyBox\s+v(?P[0-9]+\.[0-9]+\.[0-9]+)`), + Package: "busybox", + PURL: mustPURL("pkg:generic/busybox@version"), + CPEs: singleCPE("cpe:2.3:a:busybox:busybox:*:*:*:*:*:*:*:*"), + }, + { + Class: "haproxy-binary", + FileGlob: "**/haproxy", + EvidenceMatcher: evidenceMatchers( + FileContentsVersionMatcher(`(?m)HA-Proxy version (?P[0-9]+\.[0-9]+\.[0-9]+)`), + FileContentsVersionMatcher(`(?m)(?P[0-9]+\.[0-9]+\.[0-9]+)-[0-9a-zA-Z]{7}.+HAProxy version`), + ), + Package: "haproxy", + PURL: mustPURL("pkg:generic/haproxy@version"), + CPEs: singleCPE("cpe:2.3:a:haproxy:haproxy:*:*:*:*:*:*:*:*"), + }, + { + Class: "perl-binary", + FileGlob: "**/perl", + EvidenceMatcher: FileContentsVersionMatcher( + `(?m)\/usr\/local\/lib\/perl\d\/(?P[0-9]+\.[0-9]+\.[0-9]+)`), + Package: "perl", + PURL: mustPURL("pkg:generic/perl@version"), + CPEs: singleCPE("cpe:2.3:a:perl:perl:*:*:*:*:*:*:*:*"), + }, + { + Class: "php-cli-binary", + FileGlob: "**/php*", + EvidenceMatcher: fileNameTemplateVersionMatcher( + `(.*/|^)php[0-9]*$`, + `(?m)X-Powered-By: PHP\/(?P[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`), + Package: "php-cli", + PURL: mustPURL("pkg:generic/php-cli@version"), + CPEs: singleCPE("cpe:2.3:a:php:php:*:*:*:*:*:*:*:*"), + }, + { + Class: "php-fpm-binary", + FileGlob: "**/php-fpm*", + EvidenceMatcher: FileContentsVersionMatcher( + `(?m)X-Powered-By: PHP\/(?P[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`), + Package: "php-fpm", + PURL: mustPURL("pkg:generic/php-fpm@version"), + CPEs: singleCPE("cpe:2.3:a:php:php:*:*:*:*:*:*:*:*"), + }, + { + Class: "php-apache-binary", + FileGlob: "**/libphp*.so", + EvidenceMatcher: FileContentsVersionMatcher( + `(?m)X-Powered-By: PHP\/(?P[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`), + Package: "libphp", + PURL: mustPURL("pkg:generic/php@version"), + CPEs: singleCPE("cpe:2.3:a:php:php:*:*:*:*:*:*:*:*"), + }, + { + Class: "php-composer-binary", + FileGlob: "**/composer*", + EvidenceMatcher: FileContentsVersionMatcher( + `(?m)'pretty_version'\s*=>\s*'(?P[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)'`), + Package: "composer", + PURL: mustPURL("pkg:generic/composer@version"), + CPEs: singleCPE("cpe:2.3:a:getcomposer:composer:*:*:*:*:*:*:*:*"), + }, + { + Class: "httpd-binary", + FileGlob: "**/httpd", + EvidenceMatcher: FileContentsVersionMatcher( + `(?m)Apache\/(?P[0-9]+\.[0-9]+\.[0-9]+)`), + Package: "httpd", + PURL: mustPURL("pkg:generic/httpd@version"), + CPEs: singleCPE("cpe:2.3:a:apache:http_server:*:*:*:*:*:*:*:*"), + }, + { + Class: "memcached-binary", + FileGlob: "**/memcached", + EvidenceMatcher: FileContentsVersionMatcher( + `(?m)memcached\s(?P[0-9]+\.[0-9]+\.[0-9]+)`), + Package: "memcached", + PURL: mustPURL("pkg:generic/memcached@version"), + }, + { + Class: "traefik-binary", + FileGlob: "**/traefik", + EvidenceMatcher: FileContentsVersionMatcher( + // [NUL]v1.7.34[NUL] + // [NUL]2.9.6[NUL] + `(?m)\x00v?(?P[0-9]+\.[0-9]+\.[0-9]+(-alpha[0-9]|-beta[0-9]|-rc[0-9])?)\x00`), + Package: "traefik", + PURL: mustPURL("pkg:generic/traefik@version"), + }, + { + Class: "postgresql-binary", + FileGlob: "**/postgres", + EvidenceMatcher: FileContentsVersionMatcher( + // [NUL]PostgreSQL 15beta4 + // [NUL]PostgreSQL 15.1 + // [NUL]PostgreSQL 9.6.24 + // ?PostgreSQL 9.5alpha1 + `(?m)(\x00|\?)PostgreSQL (?P[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`), + Package: "postgresql", + PURL: mustPURL("pkg:generic/postgresql@version"), + }, + { + Class: "mysql-binary", + FileGlob: "**/mysql", + EvidenceMatcher: FileContentsVersionMatcher( + // ../../mysql-8.0.34 + // /mysql-5.6.51/bld/client + `(?m).*/mysql-(?P[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`), + Package: "mysql", + PURL: mustPURL("pkg:generic/mysql@version"), + CPEs: singleCPE("cpe:2.3:a:oracle:mysql:*:*:*:*:*:*:*:*"), + }, + { + Class: "mariadb-binary", + FileGlob: "**/mariadb", + EvidenceMatcher: FileContentsVersionMatcher( + // 10.6.15-MariaDB + `(?m)(?P[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)-MariaDB`), + Package: "mariadb", + PURL: mustPURL("pkg:generic/mariadb@version"), + }, + { + Class: "rust-standard-library-linux", + FileGlob: "**/libstd-????????????????.so", + EvidenceMatcher: FileContentsVersionMatcher( + // clang LLVM (rustc version 1.48.0 (7eac88abb 2020-11-16)) + `(?m)(\x00)clang LLVM \(rustc version (?P[0-9]+(\.[0-9]+)?(\.[0-9]+)) \(\w+ \d{4}\-\d{2}\-\d{2}\)`), + Package: "rust", + PURL: mustPURL("pkg:generic/rust@version"), + CPEs: singleCPE("cpe:2.3:a:rust-lang:rust:*:*:*:*:*:*:*:*"), + }, + { + Class: "rust-standard-library-macos", + FileGlob: "**/libstd-????????????????.dylib", + EvidenceMatcher: FileContentsVersionMatcher( + // c 1.48.0 (7eac88abb 2020-11-16) + `(?m)c (?P[0-9]+(\.[0-9]+)?(\.[0-9]+)) \(\w+ \d{4}\-\d{2}\-\d{2}\)`), + Package: "rust", + PURL: mustPURL("pkg:generic/rust@version"), + CPEs: singleCPE("cpe:2.3:a:rust-lang:rust:*:*:*:*:*:*:*:*"), + }, + { + Class: "ruby-binary", + FileGlob: "**/ruby", + EvidenceMatcher: evidenceMatchers( + rubyMatcher, + sharedLibraryLookup( + // try to find version information from libruby shared libraries + `^libruby\.so.*$`, + rubyMatcher), + ), + Package: "ruby", + PURL: mustPURL("pkg:generic/ruby@version"), + CPEs: singleCPE("cpe:2.3:a:ruby-lang:ruby:*:*:*:*:*:*:*:*"), + }, + { + Class: "erlang-binary", + FileGlob: "**/erlexec", + EvidenceMatcher: FileContentsVersionMatcher( + // [NUL]/usr/local/src/otp-25.3.2.7/erts/ + `(?m)\\x00/usr/local/src/otp-(?P[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+?)/erts/`, + ), + Package: "erlang", + PURL: mustPURL("pkg:generic/erlang@version"), + CPEs: singleCPE("cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"), + }, + { + Class: "consul-binary", + FileGlob: "**/consul", + EvidenceMatcher: FileContentsVersionMatcher( + // NOTE: This is brittle and may not work for past or future versions + `CONSUL_VERSION: (?P\d+\.\d+\.\d+)`, + ), + Package: "consul", + PURL: mustPURL("pkg:golang/github.com/hashicorp/consul@version"), + CPEs: singleCPE("cpe:2.3:a:hashicorp:consul:*:*:*:*:*:*:*:*"), + }, + { + Class: "nginx-binary", + FileGlob: "**/nginx", + EvidenceMatcher: FileContentsVersionMatcher( + // [NUL]nginx version: nginx/1.25.1 - fetches '1.25.1' + // [NUL]nginx version: openresty/1.21.4.1 - fetches '1.21.4' as this is the nginx version part + `(?m)(\x00|\?)nginx version: [^\/]+\/(?P[0-9]+\.[0-9]+\.[0-9]+(?:\+\d+)?(?:-\d+)?)`, + ), + Package: "nginx", + PURL: mustPURL("pkg:generic/nginx@version"), + CPEs: []cpe.CPE{ + cpe.Must("cpe:2.3:a:f5:nginx:*:*:*:*:*:*:*:*"), + cpe.Must("cpe:2.3:a:nginx:nginx:*:*:*:*:*:*:*:*"), + }, + }, + { + Class: "bash-binary", + FileGlob: "**/bash", + EvidenceMatcher: FileContentsVersionMatcher( + // @(#)Bash version 5.2.15(1) release GNU + // @(#)Bash version 5.2.0(1) alpha GNU + // @(#)Bash version 5.2.0(1) beta GNU + // @(#)Bash version 5.2.0(1) rc4 GNU + `(?m)@\(#\)Bash version (?P[0-9]+\.[0-9]+\.[0-9]+)\([0-9]\) [a-z0-9]+ GNU`, + ), + Package: "bash", + PURL: mustPURL("pkg:generic/bash@version"), + CPEs: singleCPE("cpe:2.3:a:gnu:bash:*:*:*:*:*:*:*:*"), + }, + { + Class: "openssl-binary", + FileGlob: "**/openssl", + EvidenceMatcher: FileContentsVersionMatcher( + // [NUL]OpenSSL 3.1.4' + `\x00OpenSSL (?P[0-9]+\.[0-9]+\.[0-9]+(-alpha[0-9]|-beta[0-9]|-rc[0-9])?)`, + ), + Package: "openssl", + PURL: mustPURL("pkg:generic/openssl@version"), + CPEs: singleCPE("cpe:2.3:a:openssl:openssl:*:*:*:*:*:*:*:*"), + }, + } } // in both binaries and shared libraries, the version pattern is [NUL]3.11.2[NUL] @@ -356,7 +357,7 @@ var libpythonMatcher = fileNameTemplateVersionMatcher( pythonVersionTemplate, ) -var rubyMatcher = fileContentsVersionMatcher( +var rubyMatcher = FileContentsVersionMatcher( // ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-linux] // ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5) [x86_64-linux] `(?m)ruby (?P[0-9]+\.[0-9]+\.[0-9]+(p[0-9]+)?) `) diff --git a/syft/pkg/cataloger/binary/package.go b/syft/pkg/cataloger/binary/package.go index 9b3f1e22f..125918c84 100644 --- a/syft/pkg/cataloger/binary/package.go +++ b/syft/pkg/cataloger/binary/package.go @@ -3,12 +3,15 @@ package binary import ( "reflect" + "github.com/anchore/packageurl-go" "github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" ) -func newPackage(classifier classifier, location file.Location, matchMetadata map[string]string) *pkg.Package { +var emptyPURL = packageurl.PackageURL{} + +func newPackage(classifier Classifier, location file.Location, matchMetadata map[string]string) *pkg.Package { version, ok := matchMetadata["version"] if !ok { return nil @@ -42,20 +45,12 @@ func newPackage(classifier classifier, location file.Location, matchMetadata map }, } - if classifier.Type != "" { - p.Type = classifier.Type - } - if !reflect.DeepEqual(classifier.PURL, emptyPURL) { purl := classifier.PURL purl.Version = version p.PURL = purl.ToString() } - if classifier.Language != "" { - p.Language = classifier.Language - } - p.SetID() return &p diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/custom/foo b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/custom/foo new file mode 100644 index 000000000..87a6bc148 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/custom/foo @@ -0,0 +1,3 @@ +blah +foobar 1.2.3 +baz \ No newline at end of file diff --git a/syft/pkg/cataloger/cataloger.go b/syft/pkg/cataloger/cataloger.go index ca3d40861..77ba9e501 100644 --- a/syft/pkg/cataloger/cataloger.go +++ b/syft/pkg/cataloger/cataloger.go @@ -44,7 +44,7 @@ func ImageCatalogers(cfg Config) []pkg.Cataloger { return filterCatalogers([]pkg.Cataloger{ arch.NewDBCataloger(), alpine.NewDBCataloger(), - binary.NewCataloger(), + binary.NewCataloger(cfg.Binary), cpp.NewConanInfoCataloger(), debian.NewDBCataloger(), dotnet.NewDotnetPortableExecutableCataloger(), @@ -68,7 +68,7 @@ func DirectoryCatalogers(cfg Config) []pkg.Cataloger { return filterCatalogers([]pkg.Cataloger{ arch.NewDBCataloger(), alpine.NewDBCataloger(), - binary.NewCataloger(), + binary.NewCataloger(cfg.Binary), cpp.NewConanCataloger(), dart.NewPubspecLockCataloger(), debian.NewDBCataloger(), @@ -107,7 +107,7 @@ func AllCatalogers(cfg Config) []pkg.Cataloger { return filterCatalogers([]pkg.Cataloger{ arch.NewDBCataloger(), alpine.NewDBCataloger(), - binary.NewCataloger(), + binary.NewCataloger(cfg.Binary), cpp.NewConanCataloger(), dart.NewPubspecLockCataloger(), debian.NewDBCataloger(), diff --git a/syft/pkg/cataloger/config.go b/syft/pkg/cataloger/config.go index 7a352e0a1..640f54933 100644 --- a/syft/pkg/cataloger/config.go +++ b/syft/pkg/cataloger/config.go @@ -2,6 +2,7 @@ package cataloger import ( "github.com/anchore/syft/syft/cataloging" + "github.com/anchore/syft/syft/pkg/cataloger/binary" "github.com/anchore/syft/syft/pkg/cataloger/golang" "github.com/anchore/syft/syft/pkg/cataloger/java" "github.com/anchore/syft/syft/pkg/cataloger/javascript" @@ -17,6 +18,7 @@ type Config struct { Python python.CatalogerConfig Java java.ArchiveCatalogerConfig Javascript javascript.CatalogerConfig + Binary binary.CatalogerConfig Catalogers []string Parallelism int ExcludeBinaryOverlapByOwnership bool @@ -30,6 +32,7 @@ func DefaultConfig() Config { Python: python.DefaultCatalogerConfig(), Java: java.DefaultArchiveCatalogerConfig(), Javascript: javascript.DefaultCatalogerConfig(), + Binary: binary.DefaultCatalogerConfig(), ExcludeBinaryOverlapByOwnership: true, } }