Add ability to extend the binaries cataloguers (#2469)

* Add ability to extend the binaries cataloguers

Signed-off-by: Laurent Goderre <laurent.goderre@docker.com>

* restrict binary classifier package attributes

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Laurent Goderre <laurent.goderre@docker.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Laurent Goderre 2024-01-05 15:32:07 -05:00 committed by GitHub
parent 3174a17efb
commit a16a4ad6c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 523 additions and 390 deletions

View File

@ -14,6 +14,7 @@ import (
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/cataloging" "github.com/anchore/syft/syft/cataloging"
"github.com/anchore/syft/syft/pkg/cataloger" "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" golangCataloger "github.com/anchore/syft/syft/pkg/cataloger/golang"
javaCataloger "github.com/anchore/syft/syft/pkg/cataloger/java" javaCataloger "github.com/anchore/syft/syft/pkg/cataloger/java"
javascriptCataloger "github.com/anchore/syft/syft/pkg/cataloger/javascript" javascriptCataloger "github.com/anchore/syft/syft/pkg/cataloger/javascript"
@ -150,6 +151,7 @@ func (cfg Catalog) ToCatalogerConfig() cataloger.Config {
Javascript: javascriptCataloger.DefaultCatalogerConfig(). Javascript: javascriptCataloger.DefaultCatalogerConfig().
WithSearchRemoteLicenses(cfg.Javascript.SearchRemoteLicenses). WithSearchRemoteLicenses(cfg.Javascript.SearchRemoteLicenses).
WithNpmBaseURL(cfg.Javascript.NpmBaseURL), WithNpmBaseURL(cfg.Javascript.NpmBaseURL),
Binary: binaryCataloger.DefaultCatalogerConfig(),
Python: pythonCataloger.CatalogerConfig{ Python: pythonCataloger.CatalogerConfig{
GuessUnpinnedRequirements: cfg.Python.GuessUnpinnedRequirements, GuessUnpinnedRequirements: cfg.Python.GuessUnpinnedRequirements,
}, },

View File

@ -12,8 +12,20 @@ import (
const catalogerName = "binary-cataloger" const catalogerName = "binary-cataloger"
func NewCataloger() pkg.Cataloger { type CatalogerConfig struct {
return &Cataloger{} 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, // 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- // 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 // related runtimes like Python, Go, Java, or Node. Some exceptions can be made for widely-used binaries such
// as busybox. // as busybox.
type Cataloger struct{} type Cataloger struct {
classifiers []Classifier
}
// Name returns a string that uniquely describes the Cataloger // Name returns a string that uniquely describes the Cataloger
func (c Cataloger) Name() string { 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 packages []pkg.Package
var relationships []artifact.Relationship var relationships []artifact.Relationship
for _, cls := range defaultClassifiers { for _, cls := range c.classifiers {
log.WithFields("classifier", cls.Class).Trace("cataloging binaries") log.WithFields("classifier", cls.Class).Trace("cataloging binaries")
newPkgs, err := catalog(resolver, cls) newPkgs, err := catalog(resolver, cls)
if err != nil { if err != nil {
@ -71,7 +85,7 @@ func mergePackages(target *pkg.Package, extra *pkg.Package) {
target.Metadata = meta 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) locations, err := resolver.FilesByGlob(cls.FileGlob)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -780,7 +780,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
c := NewCataloger() c := NewCataloger(DefaultCatalogerConfig())
src, err := source.NewFromDirectoryPath(test.fixtureDir) src, err := source.NewFromDirectoryPath(test.fixtureDir)
require.NoError(t, err) require.NoError(t, err)
@ -819,7 +819,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
c := NewCataloger() c := NewCataloger(DefaultCatalogerConfig())
img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureImage) img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureImage)
src, err := source.NewFromStereoscopeImageObject(img, test.fixtureImage, nil) 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) { func TestClassifierCataloger_DefaultClassifiers_NegativeCases(t *testing.T) {
c := NewCataloger() c := NewCataloger(DefaultCatalogerConfig())
src, err := source.NewFromDirectoryPath("test-fixtures/classifiers/negative") src, err := source.NewFromDirectoryPath("test-fixtures/classifiers/negative")
assert.NoError(t, err) assert.NoError(t, err)
@ -863,6 +863,129 @@ func TestClassifierCataloger_DefaultClassifiers_NegativeCases(t *testing.T) {
assert.Equal(t, 0, len(actualResults)) 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<version>[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 { func locations(locations ...string) file.LocationSet {
var locs []file.Location var locs []file.Location
for _, s := range locations { for _, s := range locations {
@ -1019,7 +1142,7 @@ func (p *panicyResolver) FileMetadataByLocation(_ file.Location) (file.Metadata,
var _ file.Resolver = (*panicyResolver)(nil) var _ file.Resolver = (*panicyResolver)(nil)
func Test_Cataloger_ResilientToErrors(t *testing.T) { func Test_Cataloger_ResilientToErrors(t *testing.T) {
c := NewCataloger() c := NewCataloger(DefaultCatalogerConfig())
resolver := &panicyResolver{} resolver := &panicyResolver{}
_, _, err := c.Catalog(resolver) _, _, err := c.Catalog(resolver)

View File

@ -20,11 +20,9 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader" "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.
// classifier is a generic package classifier that can be used to match a package definition type Classifier struct {
// to a file that meets the given content criteria of the evidenceMatcher.
type classifier struct {
Class string Class string
// FileGlob is a selector to narrow down file inspection using the **/glob* syntax // 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 // 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. // 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 // Information below is used to specify the Package information when returned
// Package is the name to use for the package // Package is the name to use for the package
Package string 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 is the Package URL to use when generating a package
PURL packageurl.PackageURL PURL packageurl.PackageURL
@ -52,11 +44,11 @@ type classifier struct {
CPEs []cpe.CPE CPEs []cpe.CPE
} }
// evidenceMatcher is a function called to catalog Packages that match some sort of evidence // 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) type EvidenceMatcher func(resolver file.Resolver, classifier Classifier, location file.Location) ([]pkg.Package, error)
func evidenceMatchers(matchers ...evidenceMatcher) evidenceMatcher { func evidenceMatchers(matchers ...EvidenceMatcher) EvidenceMatcher {
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) {
for _, matcher := range matchers { for _, matcher := range matchers {
match, err := matcher(resolver, classifier, location) match, err := matcher(resolver, classifier, location)
if err != nil { 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) 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) { if !pat.MatchString(location.RealPath) {
return nil, nil 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) 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) contents, err := getContents(resolver, location)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to get read contents for file: %w", err) return nil, fmt.Errorf("unable to get read contents for file: %w", err)
@ -136,9 +128,9 @@ func fileContentsVersionMatcher(pattern string) evidenceMatcher {
} }
//nolint:gocognit //nolint:gocognit
func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher evidenceMatcher) evidenceMatcher { func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher EvidenceMatcher) EvidenceMatcher {
pat := regexp.MustCompile(sharedLibraryPattern) 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) libs, err := sharedLibraries(resolver, location)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -13,16 +13,16 @@ func Test_ClassifierCPEs(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
fixture string fixture string
classifier classifier classifier Classifier
cpes []string cpes []string
}{ }{
{ {
name: "no CPEs", name: "no CPEs",
fixture: "test-fixtures/version.txt", fixture: "test-fixtures/version.txt",
classifier: classifier{ classifier: Classifier{
Package: "some-app", Package: "some-app",
FileGlob: "**/version.txt", FileGlob: "**/version.txt",
EvidenceMatcher: fileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`), EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`),
CPEs: []cpe.CPE{}, CPEs: []cpe.CPE{},
}, },
cpes: nil, cpes: nil,
@ -30,10 +30,10 @@ func Test_ClassifierCPEs(t *testing.T) {
{ {
name: "one CPE", name: "one CPE",
fixture: "test-fixtures/version.txt", fixture: "test-fixtures/version.txt",
classifier: classifier{ classifier: Classifier{
Package: "some-app", Package: "some-app",
FileGlob: "**/version.txt", FileGlob: "**/version.txt",
EvidenceMatcher: fileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`), EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`),
CPEs: []cpe.CPE{ CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*"), cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*"),
}, },
@ -45,10 +45,10 @@ func Test_ClassifierCPEs(t *testing.T) {
{ {
name: "multiple CPEs", name: "multiple CPEs",
fixture: "test-fixtures/version.txt", fixture: "test-fixtures/version.txt",
classifier: classifier{ classifier: Classifier{
Package: "some-app", Package: "some-app",
FileGlob: "**/version.txt", FileGlob: "**/version.txt",
EvidenceMatcher: fileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`), EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`),
CPEs: []cpe.CPE{ CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*"), cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:some:apps:*:*:*:*:*:*:*:*"), cpe.Must("cpe:2.3:a:some:apps:*:*:*:*:*:*:*:*"),

View File

@ -2,350 +2,351 @@ package binary
import ( import (
"github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg"
) )
var defaultClassifiers = []classifier{ //nolint:funlen
{ func DefaultClassifiers() []Classifier {
Class: "python-binary", return []Classifier{
FileGlob: "**/python*", {
EvidenceMatcher: evidenceMatchers( Class: "python-binary",
// try to find version information from libpython shared libraries FileGlob: "**/python*",
sharedLibraryLookup( EvidenceMatcher: evidenceMatchers(
`^libpython[0-9]+(?:\.[0-9]+)+[a-z]?\.so.*$`, // try to find version information from libpython shared libraries
libpythonMatcher), sharedLibraryLookup(
// check for version information in the binary `^libpython[0-9]+(?:\.[0-9]+)+[a-z]?\.so.*$`,
fileNameTemplateVersionMatcher( libpythonMatcher),
`(?:.*/|^)python(?P<version>[0-9]+(?:\.[0-9]+)+)$`, // check for version information in the binary
pythonVersionTemplate), fileNameTemplateVersionMatcher(
), `(?:.*/|^)python(?P<version>[0-9]+(?:\.[0-9]+)+)$`,
Package: "python", pythonVersionTemplate),
PURL: mustPURL("pkg:generic/python@version"), ),
CPEs: []cpe.CPE{ Package: "python",
cpe.Must("cpe:2.3:a:python_software_foundation:python:*:*:*:*:*:*:*:*"), PURL: mustPURL("pkg:generic/python@version"),
cpe.Must("cpe:2.3:a:python:python:*:*:*:*:*:*:*:*"), 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",
Class: "python-binary-lib", FileGlob: "**/libpython*.so*",
FileGlob: "**/libpython*.so*", EvidenceMatcher: libpythonMatcher,
EvidenceMatcher: libpythonMatcher, Package: "python",
Package: "python", PURL: mustPURL("pkg:generic/python@version"),
PURL: mustPURL("pkg:generic/python@version"), CPEs: []cpe.CPE{
CPEs: []cpe.CPE{ cpe.Must("cpe:2.3:a:python_software_foundation:python:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:python_software_foundation:python:*:*:*:*:*:*:*:*"), cpe.Must("cpe:2.3:a:python:python:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:python:python:*:*:*:*:*:*:*:*"), },
}, },
}, {
{ Class: "go-binary",
Class: "go-binary", FileGlob: "**/go",
FileGlob: "**/go", EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: fileContentsVersionMatcher( `(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)\x00`),
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)\x00`), Package: "go",
Package: "go", PURL: mustPURL("pkg:generic/go@version"),
PURL: mustPURL("pkg:generic/go@version"), CPEs: singleCPE("cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*"),
CPEs: singleCPE("cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*"),
},
{
Class: "julia-binary",
FileGlob: "**/libjulia-internal.so",
EvidenceMatcher: fileContentsVersionMatcher(
`(?m)__init__\x00(?P<version>[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<version>[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<version>\d.\d\.\d\d*)[a-z0-9]{12,15}-[0-9]{19}`),
fileContentsVersionMatcher(`(?s)\x00(?P<version>\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<release>[0-9]+[.0-9]*)\x00(?P<version>[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<release>[0-9]+[.0-9]+)\x00{4}(?P<version>[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<version>[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<version>[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<version>[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<version>[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<version>[0-9]+\.[0-9]+\.[0-9]+)`),
fileContentsVersionMatcher(`(?m)(?P<version>[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<version>[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<version>[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<version>[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<version>[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<version>[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<version>[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<version>[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<version>[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<version>[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<version>[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<version>[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<version>[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<version>[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(
// <artificial>[NUL]/usr/local/src/otp-25.3.2.7/erts/
`(?m)\<artificial\>\x00/usr/local/src/otp-(?P<version>[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<version>\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<version>[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: "julia-binary",
Class: "bash-binary", FileGlob: "**/libjulia-internal.so",
FileGlob: "**/bash", EvidenceMatcher: FileContentsVersionMatcher(
EvidenceMatcher: fileContentsVersionMatcher( `(?m)__init__\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00verify`),
// @(#)Bash version 5.2.15(1) release GNU Package: "julia",
// @(#)Bash version 5.2.0(1) alpha GNU PURL: mustPURL("pkg:generic/julia@version"),
// @(#)Bash version 5.2.0(1) beta GNU CPEs: singleCPE("cpe:2.3:a:julialang:julia:*:*:*:*:*:*:*:*"),
// @(#)Bash version 5.2.0(1) rc4 GNU },
`(?m)@\(#\)Bash version (?P<version>[0-9]+\.[0-9]+\.[0-9]+)\([0-9]\) [a-z0-9]+ GNU`, {
), Class: "helm",
Package: "bash", FileGlob: "**/helm",
PURL: mustPURL("pkg:generic/bash@version"), EvidenceMatcher: FileContentsVersionMatcher(
CPEs: singleCPE("cpe:2.3:a:gnu:bash:*:*:*:*:*:*:*:*"), `(?m)\x00v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`),
}, Package: "helm",
{ PURL: mustPURL("pkg:golang/helm.sh/helm@version"),
Class: "openssl-binary", CPEs: singleCPE("cpe:2.3:a:helm:helm:*:*:*:*:*:*:*"),
FileGlob: "**/openssl", },
EvidenceMatcher: fileContentsVersionMatcher( {
// [NUL]OpenSSL 3.1.4' Class: "redis-binary",
`\x00OpenSSL (?P<version>[0-9]+\.[0-9]+\.[0-9]+(-alpha[0-9]|-beta[0-9]|-rc[0-9])?)`, FileGlob: "**/redis-server",
), EvidenceMatcher: evidenceMatchers(
Package: "openssl", FileContentsVersionMatcher(`(?s)payload %5.*?(?P<version>\d.\d\.\d\d*)[a-z0-9]{12,15}-[0-9]{19}`),
PURL: mustPURL("pkg:generic/openssl@version"), FileContentsVersionMatcher(`(?s)\x00(?P<version>\d.\d\.\d\d*)[a-z0-9]{12}-[0-9]{19}\x00.*?payload %5`),
CPEs: singleCPE("cpe:2.3:a:openssl:openssl:*:*:*:*:*:*:*:*"), ),
}, 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<release>[0-9]+[.0-9]*)\x00(?P<version>[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<release>[0-9]+[.0-9]+)\x00{4}(?P<version>[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<version>[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<version>[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<version>[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<version>[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<version>[0-9]+\.[0-9]+\.[0-9]+)`),
FileContentsVersionMatcher(`(?m)(?P<version>[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<version>[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<version>[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<version>[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<version>[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<version>[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<version>[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<version>[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<version>[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<version>[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<version>[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<version>[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<version>[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<version>[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(
// <artificial>[NUL]/usr/local/src/otp-25.3.2.7/erts/
`(?m)\<artificial\>\x00/usr/local/src/otp-(?P<version>[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<version>\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<version>[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<version>[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<version>[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] // in both binaries and shared libraries, the version pattern is [NUL]3.11.2[NUL]
@ -356,7 +357,7 @@ var libpythonMatcher = fileNameTemplateVersionMatcher(
pythonVersionTemplate, pythonVersionTemplate,
) )
var rubyMatcher = fileContentsVersionMatcher( var rubyMatcher = FileContentsVersionMatcher(
// ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-linux] // ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-linux]
// ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5) [x86_64-linux] // ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5) [x86_64-linux]
`(?m)ruby (?P<version>[0-9]+\.[0-9]+\.[0-9]+(p[0-9]+)?) `) `(?m)ruby (?P<version>[0-9]+\.[0-9]+\.[0-9]+(p[0-9]+)?) `)

View File

@ -3,12 +3,15 @@ package binary
import ( import (
"reflect" "reflect"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "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"] version, ok := matchMetadata["version"]
if !ok { if !ok {
return nil 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) { if !reflect.DeepEqual(classifier.PURL, emptyPURL) {
purl := classifier.PURL purl := classifier.PURL
purl.Version = version purl.Version = version
p.PURL = purl.ToString() p.PURL = purl.ToString()
} }
if classifier.Language != "" {
p.Language = classifier.Language
}
p.SetID() p.SetID()
return &p return &p

View File

@ -0,0 +1,3 @@
blah
foobar 1.2.3
baz

View File

@ -44,7 +44,7 @@ func ImageCatalogers(cfg Config) []pkg.Cataloger {
return filterCatalogers([]pkg.Cataloger{ return filterCatalogers([]pkg.Cataloger{
arch.NewDBCataloger(), arch.NewDBCataloger(),
alpine.NewDBCataloger(), alpine.NewDBCataloger(),
binary.NewCataloger(), binary.NewCataloger(cfg.Binary),
cpp.NewConanInfoCataloger(), cpp.NewConanInfoCataloger(),
debian.NewDBCataloger(), debian.NewDBCataloger(),
dotnet.NewDotnetPortableExecutableCataloger(), dotnet.NewDotnetPortableExecutableCataloger(),
@ -68,7 +68,7 @@ func DirectoryCatalogers(cfg Config) []pkg.Cataloger {
return filterCatalogers([]pkg.Cataloger{ return filterCatalogers([]pkg.Cataloger{
arch.NewDBCataloger(), arch.NewDBCataloger(),
alpine.NewDBCataloger(), alpine.NewDBCataloger(),
binary.NewCataloger(), binary.NewCataloger(cfg.Binary),
cpp.NewConanCataloger(), cpp.NewConanCataloger(),
dart.NewPubspecLockCataloger(), dart.NewPubspecLockCataloger(),
debian.NewDBCataloger(), debian.NewDBCataloger(),
@ -107,7 +107,7 @@ func AllCatalogers(cfg Config) []pkg.Cataloger {
return filterCatalogers([]pkg.Cataloger{ return filterCatalogers([]pkg.Cataloger{
arch.NewDBCataloger(), arch.NewDBCataloger(),
alpine.NewDBCataloger(), alpine.NewDBCataloger(),
binary.NewCataloger(), binary.NewCataloger(cfg.Binary),
cpp.NewConanCataloger(), cpp.NewConanCataloger(),
dart.NewPubspecLockCataloger(), dart.NewPubspecLockCataloger(),
debian.NewDBCataloger(), debian.NewDBCataloger(),

View File

@ -2,6 +2,7 @@ package cataloger
import ( import (
"github.com/anchore/syft/syft/cataloging" "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/golang"
"github.com/anchore/syft/syft/pkg/cataloger/java" "github.com/anchore/syft/syft/pkg/cataloger/java"
"github.com/anchore/syft/syft/pkg/cataloger/javascript" "github.com/anchore/syft/syft/pkg/cataloger/javascript"
@ -17,6 +18,7 @@ type Config struct {
Python python.CatalogerConfig Python python.CatalogerConfig
Java java.ArchiveCatalogerConfig Java java.ArchiveCatalogerConfig
Javascript javascript.CatalogerConfig Javascript javascript.CatalogerConfig
Binary binary.CatalogerConfig
Catalogers []string Catalogers []string
Parallelism int Parallelism int
ExcludeBinaryOverlapByOwnership bool ExcludeBinaryOverlapByOwnership bool
@ -30,6 +32,7 @@ func DefaultConfig() Config {
Python: python.DefaultCatalogerConfig(), Python: python.DefaultCatalogerConfig(),
Java: java.DefaultArchiveCatalogerConfig(), Java: java.DefaultArchiveCatalogerConfig(),
Javascript: javascript.DefaultCatalogerConfig(), Javascript: javascript.DefaultCatalogerConfig(),
Binary: binary.DefaultCatalogerConfig(),
ExcludeBinaryOverlapByOwnership: true, ExcludeBinaryOverlapByOwnership: true,
} }
} }