From a35f64c971461ddd54111365dcf19fa26addc76b Mon Sep 17 00:00:00 2001 From: Brian Ebarb Date: Wed, 13 Mar 2024 14:34:06 -0500 Subject: [PATCH] feat: elf_binary_package_cataloger Signed-off-by: Brian Ebarb --- internal/task/package_tasks.go | 1 + schema/json/schema-16.0.4.json | 30 ++++ schema/json/schema-latest.json | 30 ++++ syft/internal/packagemetadata/generated.go | 1 + syft/internal/packagemetadata/names.go | 1 + syft/pkg/{binary_signature.go => binary.go} | 9 ++ syft/pkg/cataloger/binary/README.md | 2 +- syft/pkg/cataloger/binary/classifier.go | 4 +- .../{cataloger.go => classifier_cataloger.go} | 0 ...r_test.go => classifier_cataloger_test.go} | 0 .../{package.go => classifier_package.go} | 2 +- ...{default_classifiers.go => classifiers.go} | 0 syft/pkg/cataloger/binary/elf_package.go | 35 +++++ .../cataloger/binary/elf_package_cataloger.go | 147 ++++++++++++++++++ .../binary/elf_package_cataloger_test.go | 124 +++++++++++++++ syft/pkg/cataloger/binary/elf_package_test.go | 96 ++++++++++++ .../elf-test-fixtures/Dockerfile | 14 ++ .../elfsrc/hello_world.cpp | 6 + .../elfbinwithnestedlib/elfsrc/hello_world.h | 8 + .../elfbinwithnestedlib/elfsrc/makefile | 48 ++++++ .../elfbinwithnestedlib/elfsrc/testbin.cpp | 8 + .../elfbinwithnestedlib/makefile | 18 +++ .../elfsrc1/hello_world.cpp | 6 + .../elfbinwithsisterlib/elfsrc1/hello_world.h | 8 + .../elfbinwithsisterlib/elfsrc1/makefile | 48 ++++++ .../elfbinwithsisterlib/elfsrc1/testbin.cpp | 8 + .../elfsrc2/hello_world2.cpp | 6 + .../elfsrc2/hello_world2.h | 8 + .../elfbinwithsisterlib/elfsrc2/makefile | 48 ++++++ .../elfbinwithsisterlib/elfsrc2/testbin2.cpp | 8 + .../elfbinwithsisterlib/makefile | 18 +++ syft/pkg/language.go | 2 +- 32 files changed, 739 insertions(+), 5 deletions(-) rename syft/pkg/{binary_signature.go => binary.go} (64%) rename syft/pkg/cataloger/binary/{cataloger.go => classifier_cataloger.go} (100%) rename syft/pkg/cataloger/binary/{cataloger_test.go => classifier_cataloger_test.go} (100%) rename syft/pkg/cataloger/binary/{package.go => classifier_package.go} (89%) rename syft/pkg/cataloger/binary/{default_classifiers.go => classifiers.go} (100%) create mode 100644 syft/pkg/cataloger/binary/elf_package.go create mode 100644 syft/pkg/cataloger/binary/elf_package_cataloger.go create mode 100644 syft/pkg/cataloger/binary/elf_package_cataloger_test.go create mode 100644 syft/pkg/cataloger/binary/elf_package_test.go create mode 100644 syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/Dockerfile create mode 100644 syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/elfsrc/hello_world.cpp create mode 100644 syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/elfsrc/hello_world.h create mode 100644 syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/elfsrc/makefile create mode 100644 syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/elfsrc/testbin.cpp create mode 100644 syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/makefile create mode 100644 syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc1/hello_world.cpp create mode 100644 syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc1/hello_world.h create mode 100644 syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc1/makefile create mode 100644 syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc1/testbin.cpp create mode 100644 syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc2/hello_world2.cpp create mode 100644 syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc2/hello_world2.h create mode 100644 syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc2/makefile create mode 100644 syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc2/testbin2.cpp create mode 100644 syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/makefile diff --git a/internal/task/package_tasks.go b/internal/task/package_tasks.go index 4d92497d9..5afe66490 100644 --- a/internal/task/package_tasks.go +++ b/internal/task/package_tasks.go @@ -117,6 +117,7 @@ func DefaultPackageTaskFactories() PackageTaskFactories { }, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "binary", ), + newSimplePackageTaskFactory(binary.NewELFPackageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "binary", "elf-package"), newSimplePackageTaskFactory(githubactions.NewActionUsageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "github", "github-actions"), newSimplePackageTaskFactory(githubactions.NewWorkflowUsageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "github", "github-actions"), newPackageTaskFactory( diff --git a/schema/json/schema-16.0.4.json b/schema/json/schema-16.0.4.json index cba3ff985..061db0319 100644 --- a/schema/json/schema-16.0.4.json +++ b/schema/json/schema-16.0.4.json @@ -630,6 +630,33 @@ "dso" ] }, + "ElfBinaryPackageNotes": { + "properties": { + "type": { + "type": "string" + }, + "vendor": { + "type": "string" + }, + "system": { + "type": "string" + }, + "sourceRepo": { + "type": "string" + }, + "commit": { + "type": "string" + } + }, + "type": "object", + "required": [ + "type", + "vendor", + "system", + "sourceRepo", + "commit" + ] + }, "ElixirMixLockEntry": { "properties": { "name": { @@ -1410,6 +1437,9 @@ { "$ref": "#/$defs/DpkgDbEntry" }, + { + "$ref": "#/$defs/ElfBinaryPackageNotes" + }, { "$ref": "#/$defs/ElixirMixLockEntry" }, diff --git a/schema/json/schema-latest.json b/schema/json/schema-latest.json index 50e97e9be..1ecbc0db0 100644 --- a/schema/json/schema-latest.json +++ b/schema/json/schema-latest.json @@ -630,6 +630,33 @@ "dso" ] }, + "ElfBinaryPackageNotes": { + "properties": { + "type": { + "type": "string" + }, + "vendor": { + "type": "string" + }, + "system": { + "type": "string" + }, + "sourceRepo": { + "type": "string" + }, + "commit": { + "type": "string" + } + }, + "type": "object", + "required": [ + "type", + "vendor", + "system", + "sourceRepo", + "commit" + ] + }, "ElixirMixLockEntry": { "properties": { "name": { @@ -1425,6 +1452,9 @@ { "$ref": "#/$defs/DpkgDbEntry" }, + { + "$ref": "#/$defs/ElfBinaryPackageNotes" + }, { "$ref": "#/$defs/ElixirMixLockEntry" }, diff --git a/syft/internal/packagemetadata/generated.go b/syft/internal/packagemetadata/generated.go index d7ba10159..8d09d7d8b 100644 --- a/syft/internal/packagemetadata/generated.go +++ b/syft/internal/packagemetadata/generated.go @@ -19,6 +19,7 @@ func AllTypes() []any { pkg.DotnetDepsEntry{}, pkg.DotnetPortableExecutableEntry{}, pkg.DpkgDBEntry{}, + pkg.ELFBinaryPackageNotes{}, pkg.ElixirMixLockEntry{}, pkg.ErlangRebarLockEntry{}, pkg.GolangBinaryBuildinfoEntry{}, diff --git a/syft/internal/packagemetadata/names.go b/syft/internal/packagemetadata/names.go index 805d197ea..c738202fc 100644 --- a/syft/internal/packagemetadata/names.go +++ b/syft/internal/packagemetadata/names.go @@ -74,6 +74,7 @@ var jsonTypes = makeJSONTypes( jsonNames(pkg.DotnetDepsEntry{}, "dotnet-deps-entry", "DotnetDepsMetadata"), jsonNames(pkg.DotnetPortableExecutableEntry{}, "dotnet-portable-executable-entry"), jsonNames(pkg.DpkgDBEntry{}, "dpkg-db-entry", "DpkgMetadata"), + jsonNames(pkg.ELFBinaryPackageNotes{}, "elf-binary-package-notes"), jsonNames(pkg.RubyGemspec{}, "ruby-gemspec", "GemMetadata"), jsonNames(pkg.GolangBinaryBuildinfoEntry{}, "go-module-buildinfo-entry", "GolangBinMetadata", "GolangMetadata"), jsonNames(pkg.GolangModuleEntry{}, "go-module-entry", "GolangModMetadata"), diff --git a/syft/pkg/binary_signature.go b/syft/pkg/binary.go similarity index 64% rename from syft/pkg/binary_signature.go rename to syft/pkg/binary.go index ddbe4cfff..2779fc986 100644 --- a/syft/pkg/binary_signature.go +++ b/syft/pkg/binary.go @@ -12,3 +12,12 @@ type ClassifierMatch struct { Classifier string `mapstructure:"Classifier" json:"classifier"` Location file.Location `mapstructure:"Location" json:"location"` } + +// ELFBinaryPackageNotes Represents metadata captured from the .note.package section of the binary +type ELFBinaryPackageNotes struct { + Type string `json:"type"` + Vendor string `json:"vendor"` + System string `json:"system"` + Source string `json:"sourceRepo"` + Commit string `json:"commit"` +} diff --git a/syft/pkg/cataloger/binary/README.md b/syft/pkg/cataloger/binary/README.md index e42f456ad..e4cb9cd67 100644 --- a/syft/pkg/cataloger/binary/README.md +++ b/syft/pkg/cataloger/binary/README.md @@ -1,4 +1,4 @@ -# Adding tests for the Binary cataloger +# Adding tests for the Binary classifier cataloger > [!TIP] > **TL;DR** to add a test for a new classifier: diff --git a/syft/pkg/cataloger/binary/classifier.go b/syft/pkg/cataloger/binary/classifier.go index 885374260..d5e6a4418 100644 --- a/syft/pkg/cataloger/binary/classifier.go +++ b/syft/pkg/cataloger/binary/classifier.go @@ -125,7 +125,7 @@ func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate stri matchMetadata := internal.MatchNamedCaptureGroups(tmplPattern, string(contents)) - p := newPackage(classifier, location, matchMetadata) + p := newClassifierPackage(classifier, location, matchMetadata) if p == nil { return nil, nil } @@ -144,7 +144,7 @@ func FileContentsVersionMatcher(pattern string) EvidenceMatcher { matchMetadata := internal.MatchNamedCaptureGroups(pat, string(contents)) - p := newPackage(classifier, location, matchMetadata) + p := newClassifierPackage(classifier, location, matchMetadata) if p == nil { return nil, nil } diff --git a/syft/pkg/cataloger/binary/cataloger.go b/syft/pkg/cataloger/binary/classifier_cataloger.go similarity index 100% rename from syft/pkg/cataloger/binary/cataloger.go rename to syft/pkg/cataloger/binary/classifier_cataloger.go diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/classifier_cataloger_test.go similarity index 100% rename from syft/pkg/cataloger/binary/cataloger_test.go rename to syft/pkg/cataloger/binary/classifier_cataloger_test.go diff --git a/syft/pkg/cataloger/binary/package.go b/syft/pkg/cataloger/binary/classifier_package.go similarity index 89% rename from syft/pkg/cataloger/binary/package.go rename to syft/pkg/cataloger/binary/classifier_package.go index f3326e2b6..a2ed4ab6d 100644 --- a/syft/pkg/cataloger/binary/package.go +++ b/syft/pkg/cataloger/binary/classifier_package.go @@ -11,7 +11,7 @@ import ( var emptyPURL = packageurl.PackageURL{} -func newPackage(classifier Classifier, location file.Location, matchMetadata map[string]string) *pkg.Package { +func newClassifierPackage(classifier Classifier, location file.Location, matchMetadata map[string]string) *pkg.Package { version, ok := matchMetadata["version"] if !ok { return nil diff --git a/syft/pkg/cataloger/binary/default_classifiers.go b/syft/pkg/cataloger/binary/classifiers.go similarity index 100% rename from syft/pkg/cataloger/binary/default_classifiers.go rename to syft/pkg/cataloger/binary/classifiers.go diff --git a/syft/pkg/cataloger/binary/elf_package.go b/syft/pkg/cataloger/binary/elf_package.go new file mode 100644 index 000000000..fc7301675 --- /dev/null +++ b/syft/pkg/cataloger/binary/elf_package.go @@ -0,0 +1,35 @@ +package binary + +import ( + "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" +) + +func newELFPackage(metadata elfBinaryPackageNotes, locations file.LocationSet, licenses []pkg.License) pkg.Package { + p := pkg.Package{ + Name: metadata.Name, + Version: metadata.Version, + Licenses: pkg.NewLicenseSet(licenses...), + PURL: packageURL(metadata), + Type: pkg.BinaryPkg, + Locations: locations, + Metadata: metadata.ELFBinaryPackageNotes, + } + + p.SetID() + + return p +} + +func packageURL(metadata elfBinaryPackageNotes) string { + // TODO: what if the System value is not set? + return packageurl.NewPackageURL( + packageurl.TypeGeneric, + metadata.System, + metadata.Name, + metadata.Version, + nil, + "", + ).ToString() +} diff --git a/syft/pkg/cataloger/binary/elf_package_cataloger.go b/syft/pkg/cataloger/binary/elf_package_cataloger.go new file mode 100644 index 000000000..abf6c7558 --- /dev/null +++ b/syft/pkg/cataloger/binary/elf_package_cataloger.go @@ -0,0 +1,147 @@ +package binary + +import ( + "context" + "debug/elf" + "encoding/json" + "fmt" + + "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/internal/mimetype" + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/internal/unionreader" + "github.com/anchore/syft/syft/pkg" +) + +var _ pkg.Cataloger = (*elfPackageCataloger)(nil) + +type elfPackageCataloger struct { +} + +type elfBinaryPackageNotes struct { + Name string `json:"name"` + Version string `json:"version"` + PURL string `json:"purl"` + CPE string `json:"cpe"` + License string `json:"license"` + pkg.ELFBinaryPackageNotes `json:",inline"` + Location file.Location `json:"-"` +} + +type elfPackageKey struct { + Name string + Version string + PURL string + CPE string +} + +func NewELFPackageCataloger() pkg.Cataloger { + return &elfPackageCataloger{} +} + +func (c *elfPackageCataloger) Name() string { + return "elf-binary-package-cataloger" +} + +func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { + locations, err := resolver.FilesByMIMEType(mimetype.ExecutableMIMETypeSet.List()...) + if err != nil { + return nil, nil, fmt.Errorf("unable to get binary files by mime type: %w", err) + } + + // first find all ELF binaries that have notes + var notesByLocation = make(map[elfPackageKey][]elfBinaryPackageNotes) + for _, location := range locations { + reader, err := resolver.FileContentsByLocation(location) + if err != nil { + return nil, nil, fmt.Errorf("unable to get binary contents %q: %w", location.Path(), err) + } + + notes, err := c.parseElfNotes(file.LocationReadCloser{ + Location: location, + ReadCloser: reader, + }) + if err != nil { + log.Warnf("unable to parse ELF notes: %v", err) + continue + } + + if notes == nil { + continue + } + + notes.Location = location + key := elfPackageKey{ + Name: notes.Name, + Version: notes.Version, + PURL: notes.PURL, + CPE: notes.CPE, + } + notesByLocation[key] = append(notesByLocation[key], *notes) + } + + // now we have all ELF binaries that have notes, let's create packages for them. + // we do this in a second pass since it is possible that we have multiple ELF binaries with the same name and version + // which means the set of binaries collectively represent a single logical package. + var pkgs []pkg.Package + for _, notes := range notesByLocation { + noteLocations := file.NewLocationSet() + for _, note := range notes { + noteLocations.Add(note.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)) + } + + // create a package for each unique name/version pair (based on the first note found) + pkgs = append(pkgs, newELFPackage(notes[0], noteLocations, nil)) + } + + // why not return relationships? We have an executable cataloger that will note the dynamic libraries imported by + // each binary. After all files and packages are processed there is a final task that creates package-to-package + // and package-to-file relationships based on the dynamic libraries imported by each binary. + return pkgs, nil, nil +} + +func (c *elfPackageCataloger) parseElfNotes(reader file.LocationReadCloser) (*elfBinaryPackageNotes, error) { + metadata, err := getELFNotes(reader) + if err != nil { + return nil, fmt.Errorf("unable to process ELF binary: %w", err) + } + + if metadata == nil || metadata.Name == "" || metadata.Version == "" { + return nil, nil + } + + return metadata, nil +} + +func getELFNotes(r file.LocationReadCloser) (*elfBinaryPackageNotes, error) { + unionReader, err := unionreader.GetUnionReader(r) + if err != nil { + return nil, fmt.Errorf("unable to get union reader for binary: %w", err) + } + + f, err := elf.NewFile(unionReader) + if f == nil || err != nil { + log.WithFields("file", r.Location.Path(), "error", err).Trace("unable to parse binary as ELF") + return nil, nil + } + + noteSection := f.Section(".note.package") + if noteSection == nil { + return nil, nil + } + + notes, err := noteSection.Data() + if err != nil { + log.WithFields("error", err).Trace("unable to read .note.package") + return nil, err + } + + var metadata elfBinaryPackageNotes + if err := json.Unmarshal(notes, &metadata); err != nil { + log.WithFields("error", err).Trace("unable to unmarshal ELF package notes as JSON") + return nil, nil + } + + return &metadata, err +} diff --git a/syft/pkg/cataloger/binary/elf_package_cataloger_test.go b/syft/pkg/cataloger/binary/elf_package_cataloger_test.go new file mode 100644 index 000000000..4909ac8a8 --- /dev/null +++ b/syft/pkg/cataloger/binary/elf_package_cataloger_test.go @@ -0,0 +1,124 @@ +package binary + +import ( + "testing" + + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" +) + +func Test_ELF_Package_Cataloger(t *testing.T) { + expectedPkgs := []pkg.Package{ + { + Name: "libhello_world.so", + Version: "0.01", + PURL: "pkg:generic/syftsys/libhello_world.so@0.01", + FoundBy: "", + Locations: file.NewLocationSet(file.NewVirtualLocation("/usr/local/bin/elftests/elfbinwithnestedlib/bin/lib/libhello_world.so", "/usr/local/bin/elftests/elfbinwithnestedlib/bin/lib/libhello_world.so"), + file.NewVirtualLocation("/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world.so", "/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world.so"), + file.NewVirtualLocation("/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world2.so", "/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world2.so"), + ), + Language: "", + Type: pkg.BinaryPkg, + Metadata: pkg.ELFBinaryPackageNotes{ + Type: "testfixture", + Vendor: "syft", + System: "syftsys", + Source: "", + Commit: "", + }, + }, + { + Name: "syfttestfixture", + Version: "0.01", + PURL: "pkg:generic/syftsys/syfttestfixture@0.01", + FoundBy: "", + Locations: file.NewLocationSet(file.NewLocation("/usr/local/bin/elftests/elfbinwithnestedlib/bin/elfbinwithnestedlib").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + file.NewLocation("/usr/local/bin/elftests/elfbinwithsisterlib/bin/elfwithparallellibbin1").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + file.NewLocation("/usr/local/bin/elftests/elfbinwithsisterlib/bin/elfwithparallellibbin2").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + Language: "", + Type: pkg.BinaryPkg, + Metadata: pkg.ELFBinaryPackageNotes{ + Type: "testfixture", + Vendor: "syft", + System: "syftsys", + Source: "", + Commit: "", + }, + }, + } + + pkgtest.NewCatalogTester(). + WithImageResolver(t, "elf-test-fixtures"). + IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change + Expects(expectedPkgs, nil). + TestCataloger(t, NewELFPackageCataloger()) + + // expectedPkgs = []pkg.Package{ + // { + // Name: "libhello_world.so", + // Version: "0.01", + // PURL: "pkg:generic/syftsys/libhello_world.so@0.01", + // FoundBy: "", + // Locations: file.NewLocationSet(file.NewVirtualLocation("/usr/local/bin/syftelftest/lib/libhello_world.so", "/usr/local/bin/syftelftest/lib/libhello_world.so")), + // Language: "", + // Type: pkg.BinaryPkg, + // Metadata: pkg.ELFBinaryPackageNotes{ + // Type: "testfixture", + // Vendor: "syft", + // System: "syftsys", + // }, + // }, + // { + // Name: "syfttestfixture", + // Version: "0.01", + // PURL: "pkg:generic/syftsys/syfttestfixture@0.01", + // FoundBy: "", + // Locations: file.NewLocationSet(file.NewVirtualLocation("/usr/local/bin/syftelftest/bin/elfwithparallellibbin1", "/usr/local/bin/syftelftest/bin/elfwithparallellibbin1")), + // Language: "", + // Type: pkg.BinaryPkg, + // Metadata: pkg.ELFBinaryPackageNotes{ + // Type: "testfixture", + // Vendor: "syft", + // System: "syftsys", + // }, + // }, + // { + // Name: "libhello_world2.so", + // Version: "0.01", + // PURL: "pkg:generic/syftsys/libhello_world2.so@0.01", + // FoundBy: "", + // Locations: file.NewLocationSet(file.NewVirtualLocation("/usr/local/bin/syftelftest/lib/libhello_world2.so", "/usr/local/bin/syftelftest/lib/libhello_world2.so")), + // Language: "", + // Type: pkg.BinaryPkg, + // Metadata: pkg.ELFBinaryPackageNotes{ + // Type: "testfixture", + // Vendor: "syft", + // System: "syftsys", + // }, + // }, + + // { + // Name: "syfttestfixture", + // Version: "0.01", + // PURL: "pkg:generic/syftsys/syfttestfixture@0.01", + // FoundBy: "", + // Locations: file.NewLocationSet(file.NewVirtualLocation("/usr/local/bin/syftelftest/bin/elfwithparallellibbin2", "/usr/local/bin/syftelftest/bin/elfwithparallellibbin2")), + // Language: "", + // Type: pkg.BinaryPkg, + // Metadata: pkg.ELFBinaryPackageNotes{ + // Type: "testfixture", + // Vendor: "syft", + // System: "syftsys", + // }, + // }, + // } + // pkgtest.NewCatalogTester(). + // WithImageResolver(t, "elf-test-fixture-sister-lib"). + // IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change + // Expects(expectedPkgs, nil). + // TestCataloger(t, NewELFPackageCataloger()) + +} diff --git a/syft/pkg/cataloger/binary/elf_package_test.go b/syft/pkg/cataloger/binary/elf_package_test.go new file mode 100644 index 000000000..bb47e69b0 --- /dev/null +++ b/syft/pkg/cataloger/binary/elf_package_test.go @@ -0,0 +1,96 @@ +package binary + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" +) + +func Test_packageURL(t *testing.T) { + tests := []struct { + name string + notes elfBinaryPackageNotes + expected string + }{ + { + name: "elf-binary-package-cataloger", + notes: elfBinaryPackageNotes{ + Name: "github.com/anchore/syft", + Version: "v0.1.0", + ELFBinaryPackageNotes: pkg.ELFBinaryPackageNotes{ + System: "syftsys", + }, + }, + expected: "pkg:generic/syftsys/github.com/anchore/syft@v0.1.0", + }, + { + name: "elf binary package short name", + notes: elfBinaryPackageNotes{ + Name: "go.opencensus.io", + Version: "v0.23.0", + ELFBinaryPackageNotes: pkg.ELFBinaryPackageNotes{ + System: "syftsys", + }, + }, + expected: "pkg:generic/syftsys/go.opencensus.io@v0.23.0", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.expected, packageURL(test.notes)) + }) + } +} + +func Test_newELFPackage(t *testing.T) { + tests := []struct { + name string + metadata elfBinaryPackageNotes + expected pkg.Package + }{ + { + name: "elf-binary-package-cataloger", + metadata: elfBinaryPackageNotes{ + Name: "syfttestfixture", + Version: "0.01", + PURL: "pkg:generic/syftsys/syfttestfixture@0.01", + CPE: "cpe:/o:syft:syftsys_testfixture_syfttestfixture:0.01", + ELFBinaryPackageNotes: pkg.ELFBinaryPackageNotes{ + Type: "binary", + System: "syftsys", + }, + }, + + expected: pkg.Package{ + Name: "syfttestfixture", + Version: "0.01", + Type: "binary", + PURL: "pkg:generic/syftsys/syfttestfixture@0.01", + Metadata: pkg.ELFBinaryPackageNotes{ + Type: "binary", + System: "syftsys", + }, + }, + }, + } + + // for _, test := range tests { + // t.Run(test.name, func(t *testing.T) { + // assert.Equal(t, test.expected, newELFPackage(test.metadata, file.NewLocationSet(), nil)) + // }) + // } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := newELFPackage(test.metadata, file.NewLocationSet(), nil) + if diff := cmp.Diff(test.expected, actual, cmpopts.IgnoreFields(pkg.Package{}, "id"), cmpopts.IgnoreUnexported(pkg.Package{}, file.LocationSet{}, pkg.LicenseSet{})); diff != "" { + t.Errorf("newELFPackage() mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/Dockerfile b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/Dockerfile new file mode 100644 index 000000000..a5efa56eb --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/Dockerfile @@ -0,0 +1,14 @@ +FROM rockylinux:8 +RUN dnf update -y; \ + dnf install make automake gcc gcc-c++ kernel-devel -y; \ + dnf clean all +RUN mkdir -p /usr/local/bin/elftests/elfbinwithnestedlib +RUN mkdir -p /usr/local/bin/elftests/elfbinwithsisterlib +COPY ./elfbinwithnestedlib /usr/local/bin/elftests/elfbinwithnestedlib +COPY ./elfbinwithsisterlib /usr/local/bin/elftests/elfbinwithsisterlib +ENV LD_LIBRARY_PATH=/usr/local/bin/elftests/elfbinwithnestedlib/bin/lib +WORKDIR /usr/local/bin/elftests/elfbinwithnestedlib/ +RUN make +WORKDIR /usr/local/bin/elftests/elfbinwithsisterlib +RUN make + diff --git a/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/elfsrc/hello_world.cpp b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/elfsrc/hello_world.cpp new file mode 100644 index 000000000..9da59af37 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/elfsrc/hello_world.cpp @@ -0,0 +1,6 @@ +#include +#include "hello_world.h" + +void print_hello_world() { + std::cout << "Hello, World!" << std::endl; +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/elfsrc/hello_world.h b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/elfsrc/hello_world.h new file mode 100644 index 000000000..d4193a609 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/elfsrc/hello_world.h @@ -0,0 +1,8 @@ + +#ifndef HELLO_WORLD_H +#define HELLO_WORLD_H + +// Function declaration for printing "Hello, World!" to stdout +void print_hello_world(); + +#endif // HELLO_WORLD_H diff --git a/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/elfsrc/makefile b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/elfsrc/makefile new file mode 100644 index 000000000..e73f603f2 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/elfsrc/makefile @@ -0,0 +1,48 @@ +LDFLAGS := -L/lib64 -lstdc++ + +SRC_DIR := ./ +BUILD_DIR := ../build +BIN_DIR := ../bin +LIB_DIR := $(BIN_DIR)/lib + +LIB_NAME := hello_world +LIB_SRC := $(SRC_DIR)/hello_world.cpp +LIB_OBJ := $(BUILD_DIR)/$(LIB_NAME).o +LIB_SO := $(LIB_DIR)/lib$(LIB_NAME).so + +EXECUTABLE := elfbinwithnestedlib +EXEC_SRC := $(SRC_DIR)/testbin.cpp +EXEC_OBJ := $(BUILD_DIR)/$(EXECUTABLE).o + + + +all: testfixture + +$(LIB_SO): $(LIB_OBJ) | $(LIB_DIR) + $(CC) -shared -o $@ $< + echo '{"type": "testfixture","vendor": "syft","system": "syftsys","name": "libhello_world.so","version": "0.01","purl": "pkg:generic/syftsys/syfttestfixture@0.01","cpe": "cpe:/o:syft:syftsys_testfixture_syfttestfixture:0.01"}' | objcopy --add-section .note.package=/dev/stdin --set-section-flags .note.package=noload,readonly $@ + +$(LIB_OBJ): $(LIB_SRC) | $(BUILD_DIR) + $(CC) $(CFLAGS) -fPIC -c $< -o $@ + +$(EXEC_OBJ): $(EXEC_SRC) | $(BUILD_DIR) + $(CC) $(CFLAGS) -c $< -o $@ + +$(BIN_DIR): + mkdir -p $(BIN_DIR) +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) +$(LIB_DIR): + mkdir -p $(LIB_DIR) + +$(BIN_DIR)/$(EXECUTABLE): $(EXEC_OBJ) $(LIB_SO) | $(BIN_DIR) + $(CC) $(CFLAGS) -o $@ $^ -L$(LIB_DIR) -l$(LIB_NAME) $(LDFLAGS) + echo '{"type": "testfixture","vendor": "syft","system": "syftsys","name": "syfttestfixture","version": "0.01","purl": "pkg:generic/syftsys/syfttestfixture@0.01","cpe": "cpe:/o:syft:syftsys_testfixture_syfttestfixture:0.01"}' | objcopy --add-section .note.package=/dev/stdin --set-section-flags .note.package=noload,readonly $@ + +testfixture: $(BIN_DIR)/$(EXECUTABLE) + +clean: + rm -rf $(BUILD_DIR) $(LIB_DIR) $(BIN_DIR) $(EXECUTABLE) + +.PHONY: all clean + diff --git a/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/elfsrc/testbin.cpp b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/elfsrc/testbin.cpp new file mode 100644 index 000000000..58a6e1050 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/elfsrc/testbin.cpp @@ -0,0 +1,8 @@ +#include "hello_world.h" + +int main() { + // Call the function from the shared library + print_hello_world(); + + return 0; +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/makefile b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/makefile new file mode 100644 index 000000000..b53f429a3 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithnestedlib/makefile @@ -0,0 +1,18 @@ +CC = g++ +CFLAGS = -std=c++17 -Wall -Wextra -pedantic +BUILD_DIR := ./build +BIN_DIR := ./bin +LIB_DIR := $(BIN_DIR)/lib + + + +all: testfixtures + +testfixtures: + $(MAKE) -C elfsrc + +clean: + rm -rf $(BUILD_DIR) $(BIN_DIR) + +.PHONY: all clean testfixtures + diff --git a/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc1/hello_world.cpp b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc1/hello_world.cpp new file mode 100644 index 000000000..9da59af37 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc1/hello_world.cpp @@ -0,0 +1,6 @@ +#include +#include "hello_world.h" + +void print_hello_world() { + std::cout << "Hello, World!" << std::endl; +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc1/hello_world.h b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc1/hello_world.h new file mode 100644 index 000000000..d4193a609 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc1/hello_world.h @@ -0,0 +1,8 @@ + +#ifndef HELLO_WORLD_H +#define HELLO_WORLD_H + +// Function declaration for printing "Hello, World!" to stdout +void print_hello_world(); + +#endif // HELLO_WORLD_H diff --git a/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc1/makefile b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc1/makefile new file mode 100644 index 000000000..0d115202a --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc1/makefile @@ -0,0 +1,48 @@ +LDFLAGS := -L/lib64 -lstdc++ + +SRC_DIR := ./ +BUILD_DIR := ../build +LIB_DIR := ../lib +BIN_DIR := ../bin + +LIB_NAME := hello_world +LIB_SRC := $(SRC_DIR)/hello_world.cpp +LIB_OBJ := $(BUILD_DIR)/$(LIB_NAME).o +LIB_SO := $(LIB_DIR)/lib$(LIB_NAME).so + +EXECUTABLE := elfwithparallellibbin1 +EXEC_SRC := $(SRC_DIR)/testbin.cpp +EXEC_OBJ := $(BUILD_DIR)/$(EXECUTABLE).o + + + +all: testfixture + +$(LIB_SO): $(LIB_OBJ) | $(LIB_DIR) + $(CC) -shared -o $@ $< + echo '{"type": "testfixture","vendor": "syft","system": "syftsys","name": "libhello_world.so","version": "0.01","purl": "pkg:generic/syftsys/syfttestfixture@0.01","cpe": "cpe:/o:syft:syftsys_testfixture_syfttestfixture:0.01"}' | objcopy --add-section .note.package=/dev/stdin --set-section-flags .note.package=noload,readonly $@ + +$(LIB_OBJ): $(LIB_SRC) | $(BUILD_DIR) + $(CC) $(CFLAGS) -fPIC -c $< -o $@ + +$(EXEC_OBJ): $(EXEC_SRC) | $(BUILD_DIR) + $(CC) $(CFLAGS) -c $< -o $@ + +$(BIN_DIR): + mkdir -p $(BIN_DIR) +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) +$(LIB_DIR): + mkdir -p $(LIB_DIR) + +$(BIN_DIR)/$(EXECUTABLE): $(EXEC_OBJ) $(LIB_SO) | $(BIN_DIR) + $(CC) $(CFLAGS) -o $@ $^ -L$(LIB_DIR) -l$(LIB_NAME) $(LDFLAGS) + echo '{"type": "testfixture","vendor": "syft","system": "syftsys","name": "syfttestfixture","version": "0.01","purl": "pkg:generic/syftsys/syfttestfixture@0.01","cpe": "cpe:/o:syft:syftsys_testfixture_syfttestfixture:0.01"}' | objcopy --add-section .note.package=/dev/stdin --set-section-flags .note.package=noload,readonly $@ + +testfixture: $(BIN_DIR)/$(EXECUTABLE) + +clean: + rm -rf $(BUILD_DIR) $(LIB_DIR) $(BIN_DIR) $(EXECUTABLE) + +.PHONY: all clean + diff --git a/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc1/testbin.cpp b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc1/testbin.cpp new file mode 100644 index 000000000..58a6e1050 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc1/testbin.cpp @@ -0,0 +1,8 @@ +#include "hello_world.h" + +int main() { + // Call the function from the shared library + print_hello_world(); + + return 0; +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc2/hello_world2.cpp b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc2/hello_world2.cpp new file mode 100644 index 000000000..669e84a3e --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc2/hello_world2.cpp @@ -0,0 +1,6 @@ +#include +#include "hello_world2.h" + +void print_hello_world() { + std::cout << "Hello, World again!!" << std::endl; +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc2/hello_world2.h b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc2/hello_world2.h new file mode 100644 index 000000000..f611ecbd9 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc2/hello_world2.h @@ -0,0 +1,8 @@ + +#ifndef HELLO_WORLD2_H +#define HELLO_WORLD2_H + +// Function declaration for printing "Hello, World!" to stdout +void print_hello_world(); + +#endif // HELLO_WORLD2_H diff --git a/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc2/makefile b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc2/makefile new file mode 100644 index 000000000..8124389f0 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc2/makefile @@ -0,0 +1,48 @@ +LDFLAGS := -L/lib64 -lstdc++ + +SRC_DIR := ./ +BUILD_DIR := ../build +LIB_DIR := ../lib +BIN_DIR := ../bin + +LIB_NAME := hello_world2 +LIB_SRC := $(SRC_DIR)/hello_world2.cpp +LIB_OBJ := $(BUILD_DIR)/$(LIB_NAME).o +LIB_SO := $(LIB_DIR)/lib$(LIB_NAME).so + +EXECUTABLE := elfwithparallellibbin2 +EXEC_SRC := $(SRC_DIR)/testbin2.cpp +EXEC_OBJ := $(BUILD_DIR)/$(EXECUTABLE).o + + + +all: testfixture + +$(LIB_SO): $(LIB_OBJ) | $(LIB_DIR) + $(CC) -shared -o $@ $< + echo '{"type": "testfixture","vendor": "syft","system": "syftsys","name": "libhello_world.so","version": "0.01","purl": "pkg:generic/syftsys/syfttestfixture@0.01","cpe": "cpe:/o:syft:syftsys_testfixture_syfttestfixture:0.01"}' | objcopy --add-section .note.package=/dev/stdin --set-section-flags .note.package=noload,readonly $@ + +$(LIB_OBJ): $(LIB_SRC) | $(BUILD_DIR) + $(CC) $(CFLAGS) -fPIC -c $< -o $@ + +$(EXEC_OBJ): $(EXEC_SRC) | $(BUILD_DIR) + $(CC) $(CFLAGS) -c $< -o $@ + +$(BIN_DIR): + mkdir -p $(BIN_DIR) +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) +$(LIB_DIR): + mkdir -p $(LIB_DIR) + +$(BIN_DIR)/$(EXECUTABLE): $(EXEC_OBJ) $(LIB_SO) | $(BIN_DIR) + $(CC) $(CFLAGS) -o $@ $^ -L$(LIB_DIR) -l$(LIB_NAME) $(LDFLAGS) + echo '{"type": "testfixture","vendor": "syft","system": "syftsys","name": "syfttestfixture","version": "0.01","purl": "pkg:generic/syftsys/syfttestfixture@0.01","cpe": "cpe:/o:syft:syftsys_testfixture_syfttestfixture:0.01"}' | objcopy --add-section .note.package=/dev/stdin --set-section-flags .note.package=noload,readonly $@ + +testfixture: $(BIN_DIR)/$(EXECUTABLE) + +clean: + rm -rf $(BUILD_DIR) $(LIB_DIR) $(BIN_DIR) $(EXECUTABLE) + +.PHONY: all clean + diff --git a/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc2/testbin2.cpp b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc2/testbin2.cpp new file mode 100644 index 000000000..719cb8dba --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/elfsrc2/testbin2.cpp @@ -0,0 +1,8 @@ +#include "hello_world2.h" + +int main() { + // Call the function from the shared library + print_hello_world(); + + return 0; +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/makefile b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/makefile new file mode 100644 index 000000000..e72541d7b --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures/elfbinwithsisterlib/makefile @@ -0,0 +1,18 @@ +CC = g++ +CFLAGS = -std=c++17 -Wall -Wextra -pedantic +BUILD_DIR := ./build +LIB_DIR := ./lib +BIN_DIR := ./bin + + +all: testfixtures + +testfixtures: + $(MAKE) -C elfsrc1 + $(MAKE) -C elfsrc2 + +clean: + rm -rf $(BUILD_DIR) $(LIB_DIR) $(BIN_DIR) + +.PHONY: all clean testfixtures + diff --git a/syft/pkg/language.go b/syft/pkg/language.go index b0e3641fb..347c22508 100644 --- a/syft/pkg/language.go +++ b/syft/pkg/language.go @@ -82,7 +82,7 @@ func LanguageByName(name string) Language { return Dart case string(Dotnet), ".net", packageurl.TypeNuget: return Dotnet - case packageurl.TypeCocoapods, packageurl.TypeSwift, string(CocoapodsPkg): + case packageurl.TypeCocoapods, packageurl.TypeSwift, string(CocoapodsPkg), string(SwiftPkg): return Swift case packageurl.TypeConan, string(CPP): return CPP