From c5b69f8034386f2d188982a195772456025ddb4b 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 --- .github/workflows/validations.yaml | 24 +- Taskfile.yaml | 9 +- internal/constants.go | 2 +- internal/task/package_tasks.go | 1 + schema/json/schema-16.0.4.json | 30 + schema/json/schema-16.0.5.json | 2319 +++++++++++++++++ schema/json/schema-latest.json | 49 +- syft/file/cataloger/executable/cataloger.go | 37 +- syft/file/cataloger/executable/elf.go | 59 +- syft/file/cataloger/executable/elf_test.go | 84 +- syft/file/cataloger/executable/macho.go | 92 + syft/file/cataloger/executable/macho_test.go | 80 + syft/file/cataloger/executable/pe.go | 84 + syft/file/cataloger/executable/pe_test.go | 80 + .../executable/test-fixtures/elf/.gitignore | 3 + .../test-fixtures/{ => elf}/Dockerfile | 0 .../test-fixtures/{ => elf}/Makefile | 0 .../{ => elf}/differ/__main__.py | 0 .../test-fixtures/{ => elf}/expected_verify | 0 .../test-fixtures/{ => elf}/project/Makefile | 0 .../test-fixtures/{ => elf}/project/lib.c | 0 .../test-fixtures/{ => elf}/project/lib.h | 0 .../test-fixtures/{ => elf}/project/main.c | 0 .../test-fixtures/shared-info/.gitignore | 1 + .../test-fixtures/shared-info/Dockerfile | 16 + .../test-fixtures/shared-info/Makefile | 25 + .../shared-info/project/Makefile | 10 + .../shared-info/project/hello/Makefile | 23 + .../shared-info/project/hello/hello.c | 6 + .../shared-info/project/libhello/Makefile | 23 + .../shared-info/project/libhello/hello.c | 5 + .../shared-info/project/libhello/hello.h | 6 + syft/file/executable.go | 5 +- syft/format/syftjson/decoder_test.go | 2 +- syft/format/syftjson/to_syft_model_test.go | 4 +- 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/cataloger/dotnet/package_test.go | 3 +- syft/pkg/language.go | 2 +- 65 files changed, 3705 insertions(+), 60 deletions(-) create mode 100644 schema/json/schema-16.0.5.json create mode 100644 syft/file/cataloger/executable/macho.go create mode 100644 syft/file/cataloger/executable/macho_test.go create mode 100644 syft/file/cataloger/executable/pe.go create mode 100644 syft/file/cataloger/executable/pe_test.go create mode 100644 syft/file/cataloger/executable/test-fixtures/elf/.gitignore rename syft/file/cataloger/executable/test-fixtures/{ => elf}/Dockerfile (100%) rename syft/file/cataloger/executable/test-fixtures/{ => elf}/Makefile (100%) rename syft/file/cataloger/executable/test-fixtures/{ => elf}/differ/__main__.py (100%) rename syft/file/cataloger/executable/test-fixtures/{ => elf}/expected_verify (100%) rename syft/file/cataloger/executable/test-fixtures/{ => elf}/project/Makefile (100%) rename syft/file/cataloger/executable/test-fixtures/{ => elf}/project/lib.c (100%) rename syft/file/cataloger/executable/test-fixtures/{ => elf}/project/lib.h (100%) rename syft/file/cataloger/executable/test-fixtures/{ => elf}/project/main.c (100%) create mode 100644 syft/file/cataloger/executable/test-fixtures/shared-info/.gitignore create mode 100644 syft/file/cataloger/executable/test-fixtures/shared-info/Dockerfile create mode 100644 syft/file/cataloger/executable/test-fixtures/shared-info/Makefile create mode 100644 syft/file/cataloger/executable/test-fixtures/shared-info/project/Makefile create mode 100644 syft/file/cataloger/executable/test-fixtures/shared-info/project/hello/Makefile create mode 100644 syft/file/cataloger/executable/test-fixtures/shared-info/project/hello/hello.c create mode 100644 syft/file/cataloger/executable/test-fixtures/shared-info/project/libhello/Makefile create mode 100644 syft/file/cataloger/executable/test-fixtures/shared-info/project/libhello/hello.c create mode 100644 syft/file/cataloger/executable/test-fixtures/shared-info/project/libhello/hello.h 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/.github/workflows/validations.yaml b/.github/workflows/validations.yaml index 623f55c62ddb..8350c947e352 100644 --- a/.github/workflows/validations.yaml +++ b/.github/workflows/validations.yaml @@ -17,7 +17,7 @@ jobs: name: "Static analysis" runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2 - name: Bootstrap environment uses: ./.github/actions/bootstrap @@ -31,7 +31,7 @@ jobs: name: "Unit tests" runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2 - name: Bootstrap environment uses: ./.github/actions/bootstrap @@ -39,8 +39,14 @@ jobs: - name: Restore file executable test-fixture cache uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1 with: - path: syft/file/cataloger/executable/test-fixtures/bin - key: ${{ runner.os }}-unit-file-executable-cache-${{ hashFiles( 'syft/file/cataloger/executable/test-fixtures/cache.fingerprint' ) }} + path: syft/file/cataloger/executable/test-fixtures/elf/bin + key: ${{ runner.os }}-unit-file-executable-elf-cache-${{ hashFiles( 'syft/file/cataloger/executable/test-fixtures/elf/cache.fingerprint' ) }} + + - name: Restore file executable shared-info test-fixture cache + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1 + with: + path: syft/file/cataloger/executable/test-fixtures/shared-info/bin + key: ${{ runner.os }}-unit-file-executable-shared-info-cache-${{ hashFiles( 'syft/file/cataloger/executable/test-fixtures/shared-info/cache.fingerprint' ) }} - name: Restore Java test-fixture cache uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 #v4.0.1 @@ -81,7 +87,7 @@ jobs: name: "Integration tests" runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2 - name: Bootstrap environment uses: ./.github/actions/bootstrap @@ -103,7 +109,7 @@ jobs: name: "Build snapshot artifacts" runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2 - name: Bootstrap environment uses: ./.github/actions/bootstrap @@ -133,7 +139,7 @@ jobs: needs: [Build-Snapshot-Artifacts] runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2 - name: Bootstrap environment uses: ./.github/actions/bootstrap @@ -181,7 +187,7 @@ jobs: needs: [Build-Snapshot-Artifacts] runs-on: macos-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2 - name: Bootstrap environment uses: ./.github/actions/bootstrap @@ -226,7 +232,7 @@ jobs: needs: [Build-Snapshot-Artifacts] runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 #v4.1.2 - name: Bootstrap environment uses: ./.github/actions/bootstrap diff --git a/Taskfile.yaml b/Taskfile.yaml index 363b10350f59..7261a36f225f 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -266,7 +266,8 @@ tasks: desc: Generate test fixture fingerprints generates: - cmd/syft/internal/test/integration/test-fixtures/cache.fingerprint - - syft/file/cataloger/executable/test-fixtures/cache.fingerprint + - syft/file/cataloger/executable/test-fixtures/elf/cache.fingerprint + - syft/file/cataloger/executable/test-fixtures/shared-info/cache.fingerprint - syft/pkg/cataloger/binary/test-fixtures/cache.fingerprint - syft/pkg/cataloger/java/test-fixtures/java-builds/cache.fingerprint - syft/pkg/cataloger/golang/test-fixtures/archs/binaries.fingerprint @@ -276,7 +277,8 @@ tasks: - test/cli/test-fixtures/cache.fingerprint cmds: # for EXECUTABLE unit test fixtures - - "cd syft/file/cataloger/executable/test-fixtures && make cache.fingerprint" + - "cd syft/file/cataloger/executable/test-fixtures/elf && make cache.fingerprint" + - "cd syft/file/cataloger/executable/test-fixtures/shared-info && make cache.fingerprint" # for IMAGE integration test fixtures - "cd cmd/syft/internal/test/integration/test-fixtures && make cache.fingerprint" # for BINARY unit test fixtures @@ -297,7 +299,8 @@ tasks: fixtures: desc: Generate test fixtures cmds: - - "cd syft/file/cataloger/executable/test-fixtures && make" + - "cd syft/file/cataloger/executable/test-fixtures/elf && make" + - "cd syft/file/cataloger/executable/test-fixtures/shared-info && make" - "cd syft/pkg/cataloger/java/test-fixtures/java-builds && make" - "cd syft/pkg/cataloger/redhat/test-fixtures && make" - "cd syft/pkg/cataloger/binary/test-fixtures && make" diff --git a/internal/constants.go b/internal/constants.go index da5a77e49047..5f656c5f705f 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -3,5 +3,5 @@ package internal const ( // JSONSchemaVersion is the current schema version output by the JSON encoder // This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment. - JSONSchemaVersion = "16.0.4" + JSONSchemaVersion = "16.0.5" ) diff --git a/internal/task/package_tasks.go b/internal/task/package_tasks.go index 4d92497d9130..5afe66490ad2 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 cba3ff9855b2..061db0319a72 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-16.0.5.json b/schema/json/schema-16.0.5.json new file mode 100644 index 000000000000..50e97e9be9f2 --- /dev/null +++ b/schema/json/schema-16.0.5.json @@ -0,0 +1,2319 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "anchore.io/schema/syft/json/16.0.5/document", + "$ref": "#/$defs/Document", + "$defs": { + "AlpmDbEntry": { + "properties": { + "basepackage": { + "type": "string" + }, + "package": { + "type": "string" + }, + "version": { + "type": "string" + }, + "description": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "packager": { + "type": "string" + }, + "url": { + "type": "string" + }, + "validation": { + "type": "string" + }, + "reason": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + }, + "backup": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "basepackage", + "package", + "version", + "description", + "architecture", + "size", + "packager", + "url", + "validation", + "reason", + "files", + "backup" + ] + }, + "AlpmFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "type": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "gid": { + "type": "string" + }, + "time": { + "type": "string", + "format": "date-time" + }, + "size": { + "type": "string" + }, + "link": { + "type": "string" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object" + }, + "ApkDbEntry": { + "properties": { + "package": { + "type": "string" + }, + "originPackage": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "version": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "installedSize": { + "type": "integer" + }, + "pullDependencies": { + "items": { + "type": "string" + }, + "type": "array" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "pullChecksum": { + "type": "string" + }, + "gitCommitOfApkPort": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/ApkFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "originPackage", + "maintainer", + "version", + "architecture", + "url", + "description", + "size", + "installedSize", + "pullDependencies", + "provides", + "pullChecksum", + "gitCommitOfApkPort", + "files" + ] + }, + "ApkFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "ownerUid": { + "type": "string" + }, + "ownerGid": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "BinarySignature": { + "properties": { + "matches": { + "items": { + "$ref": "#/$defs/ClassifierMatch" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "matches" + ] + }, + "CConanFileEntry": { + "properties": { + "ref": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CConanInfoEntry": { + "properties": { + "ref": { + "type": "string" + }, + "package_id": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CConanLockEntry": { + "properties": { + "ref": { + "type": "string" + }, + "package_id": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "build_requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "py_requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "options": { + "$ref": "#/$defs/KeyValues" + }, + "path": { + "type": "string" + }, + "context": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CConanLockV2Entry": { + "properties": { + "ref": { + "type": "string" + }, + "packageID": { + "type": "string" + }, + "username": { + "type": "string" + }, + "channel": { + "type": "string" + }, + "recipeRevision": { + "type": "string" + }, + "packageRevision": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CPE": { + "properties": { + "cpe": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "type": "object", + "required": [ + "cpe" + ] + }, + "ClassifierMatch": { + "properties": { + "classifier": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Location" + } + }, + "type": "object", + "required": [ + "classifier", + "location" + ] + }, + "CocoaPodfileLockEntry": { + "properties": { + "checksum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "checksum" + ] + }, + "Coordinates": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "DartPubspecLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "hosted_url": { + "type": "string" + }, + "vcs_url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Descriptor": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "configuration": true + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Digest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "Document": { + "properties": { + "artifacts": { + "items": { + "$ref": "#/$defs/Package" + }, + "type": "array" + }, + "artifactRelationships": { + "items": { + "$ref": "#/$defs/Relationship" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/File" + }, + "type": "array" + }, + "source": { + "$ref": "#/$defs/Source" + }, + "distro": { + "$ref": "#/$defs/LinuxRelease" + }, + "descriptor": { + "$ref": "#/$defs/Descriptor" + }, + "schema": { + "$ref": "#/$defs/Schema" + } + }, + "type": "object", + "required": [ + "artifacts", + "artifactRelationships", + "source", + "distro", + "descriptor", + "schema" + ] + }, + "DotnetDepsEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "path": { + "type": "string" + }, + "sha512": { + "type": "string" + }, + "hashPath": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "path", + "sha512", + "hashPath" + ] + }, + "DotnetPortableExecutableEntry": { + "properties": { + "assemblyVersion": { + "type": "string" + }, + "legalCopyright": { + "type": "string" + }, + "comments": { + "type": "string" + }, + "internalName": { + "type": "string" + }, + "companyName": { + "type": "string" + }, + "productName": { + "type": "string" + }, + "productVersion": { + "type": "string" + } + }, + "type": "object", + "required": [ + "assemblyVersion", + "legalCopyright", + "companyName", + "productName", + "productVersion" + ] + }, + "DpkgDbEntry": { + "properties": { + "package": { + "type": "string" + }, + "source": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "installedSize": { + "type": "integer" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preDepends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/DpkgFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "source", + "version", + "sourceVersion", + "architecture", + "maintainer", + "installedSize", + "files" + ] + }, + "DpkgFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "isConfigFile": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "path", + "isConfigFile" + ] + }, + "ELFSecurityFeatures": { + "properties": { + "symbolTableStripped": { + "type": "boolean" + }, + "stackCanary": { + "type": "boolean" + }, + "nx": { + "type": "boolean" + }, + "relRO": { + "type": "string" + }, + "pie": { + "type": "boolean" + }, + "dso": { + "type": "boolean" + }, + "safeStack": { + "type": "boolean" + }, + "cfi": { + "type": "boolean" + }, + "fortify": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "symbolTableStripped", + "nx", + "relRO", + "pie", + "dso" + ] + }, + "ElixirMixLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "ErlangRebarLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "Executable": { + "properties": { + "format": { + "type": "string" + }, + "hasExports": { + "type": "boolean" + }, + "hasEntrypoint": { + "type": "boolean" + }, + "importedLibraries": { + "items": { + "type": "string" + }, + "type": "array" + }, + "elfSecurityFeatures": { + "$ref": "#/$defs/ELFSecurityFeatures" + } + }, + "type": "object", + "required": [ + "format", + "hasExports", + "hasEntrypoint", + "importedLibraries" + ] + }, + "File": { + "properties": { + "id": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Coordinates" + }, + "metadata": { + "$ref": "#/$defs/FileMetadataEntry" + }, + "contents": { + "type": "string" + }, + "digests": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + }, + "licenses": { + "items": { + "$ref": "#/$defs/FileLicense" + }, + "type": "array" + }, + "executable": { + "$ref": "#/$defs/Executable" + } + }, + "type": "object", + "required": [ + "id", + "location" + ] + }, + "FileLicense": { + "properties": { + "value": { + "type": "string" + }, + "spdxExpression": { + "type": "string" + }, + "type": { + "type": "string" + }, + "evidence": { + "$ref": "#/$defs/FileLicenseEvidence" + } + }, + "type": "object", + "required": [ + "value", + "spdxExpression", + "type" + ] + }, + "FileLicenseEvidence": { + "properties": { + "confidence": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "extent": { + "type": "integer" + } + }, + "type": "object", + "required": [ + "confidence", + "offset", + "extent" + ] + }, + "FileMetadataEntry": { + "properties": { + "mode": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "linkDestination": { + "type": "string" + }, + "userID": { + "type": "integer" + }, + "groupID": { + "type": "integer" + }, + "mimeType": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "type": "object", + "required": [ + "mode", + "type", + "userID", + "groupID", + "mimeType", + "size" + ] + }, + "GoModuleBuildinfoEntry": { + "properties": { + "goBuildSettings": { + "$ref": "#/$defs/KeyValues" + }, + "goCompiledVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "h1Digest": { + "type": "string" + }, + "mainModule": { + "type": "string" + }, + "goCryptoSettings": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "goCompiledVersion", + "architecture" + ] + }, + "GoModuleEntry": { + "properties": { + "h1Digest": { + "type": "string" + } + }, + "type": "object" + }, + "HaskellHackageStackEntry": { + "properties": { + "pkgHash": { + "type": "string" + } + }, + "type": "object" + }, + "HaskellHackageStackLockEntry": { + "properties": { + "pkgHash": { + "type": "string" + }, + "snapshotURL": { + "type": "string" + } + }, + "type": "object" + }, + "IDLikes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "JavaArchive": { + "properties": { + "virtualPath": { + "type": "string" + }, + "manifest": { + "$ref": "#/$defs/JavaManifest" + }, + "pomProperties": { + "$ref": "#/$defs/JavaPomProperties" + }, + "pomProject": { + "$ref": "#/$defs/JavaPomProject" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "virtualPath" + ] + }, + "JavaManifest": { + "properties": { + "main": { + "$ref": "#/$defs/KeyValues" + }, + "sections": { + "items": { + "$ref": "#/$defs/KeyValues" + }, + "type": "array" + } + }, + "type": "object" + }, + "JavaPomParent": { + "properties": { + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object", + "required": [ + "groupId", + "artifactId", + "version" + ] + }, + "JavaPomProject": { + "properties": { + "path": { + "type": "string" + }, + "parent": { + "$ref": "#/$defs/JavaPomParent" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "groupId", + "artifactId", + "version", + "name" + ] + }, + "JavaPomProperties": { + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "extraFields": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path", + "name", + "groupId", + "artifactId", + "version" + ] + }, + "JavascriptNpmPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "private": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "homepage", + "description", + "url", + "private" + ] + }, + "JavascriptNpmPackageLockEntry": { + "properties": { + "resolved": { + "type": "string" + }, + "integrity": { + "type": "string" + } + }, + "type": "object", + "required": [ + "resolved", + "integrity" + ] + }, + "JavascriptYarnLockEntry": { + "properties": { + "resolved": { + "type": "string" + }, + "integrity": { + "type": "string" + } + }, + "type": "object", + "required": [ + "resolved", + "integrity" + ] + }, + "KeyValue": { + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "key", + "value" + ] + }, + "KeyValues": { + "items": { + "$ref": "#/$defs/KeyValue" + }, + "type": "array" + }, + "License": { + "properties": { + "value": { + "type": "string" + }, + "spdxExpression": { + "type": "string" + }, + "type": { + "type": "string" + }, + "urls": { + "items": { + "type": "string" + }, + "type": "array" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "value", + "spdxExpression", + "type", + "urls", + "locations" + ] + }, + "LinuxKernelArchive": { + "properties": { + "name": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "version": { + "type": "string" + }, + "extendedVersion": { + "type": "string" + }, + "buildTime": { + "type": "string" + }, + "author": { + "type": "string" + }, + "format": { + "type": "string" + }, + "rwRootFS": { + "type": "boolean" + }, + "swapDevice": { + "type": "integer" + }, + "rootDevice": { + "type": "integer" + }, + "videoMode": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "architecture", + "version" + ] + }, + "LinuxKernelModule": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "path": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "license": { + "type": "string" + }, + "kernelVersion": { + "type": "string" + }, + "versionMagic": { + "type": "string" + }, + "parameters": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/LinuxKernelModuleParameter" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "LinuxKernelModuleParameter": { + "properties": { + "type": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "type": "object" + }, + "LinuxRelease": { + "properties": { + "prettyName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "idLike": { + "$ref": "#/$defs/IDLikes" + }, + "version": { + "type": "string" + }, + "versionID": { + "type": "string" + }, + "versionCodename": { + "type": "string" + }, + "buildID": { + "type": "string" + }, + "imageID": { + "type": "string" + }, + "imageVersion": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "variantID": { + "type": "string" + }, + "homeURL": { + "type": "string" + }, + "supportURL": { + "type": "string" + }, + "bugReportURL": { + "type": "string" + }, + "privacyPolicyURL": { + "type": "string" + }, + "cpeName": { + "type": "string" + }, + "supportEnd": { + "type": "string" + } + }, + "type": "object" + }, + "Location": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + }, + "accessPath": { + "type": "string" + }, + "annotations": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path", + "accessPath" + ] + }, + "MicrosoftKbPatch": { + "properties": { + "product_id": { + "type": "string" + }, + "kb": { + "type": "string" + } + }, + "type": "object", + "required": [ + "product_id", + "kb" + ] + }, + "NixStoreEntry": { + "properties": { + "outputHash": { + "type": "string" + }, + "output": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "outputHash", + "files" + ] + }, + "Package": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "foundBy": { + "type": "string" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + }, + "licenses": { + "$ref": "#/$defs/licenses" + }, + "language": { + "type": "string" + }, + "cpes": { + "$ref": "#/$defs/cpes" + }, + "purl": { + "type": "string" + }, + "metadataType": { + "type": "string" + }, + "metadata": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AlpmDbEntry" + }, + { + "$ref": "#/$defs/ApkDbEntry" + }, + { + "$ref": "#/$defs/BinarySignature" + }, + { + "$ref": "#/$defs/CConanFileEntry" + }, + { + "$ref": "#/$defs/CConanInfoEntry" + }, + { + "$ref": "#/$defs/CConanLockEntry" + }, + { + "$ref": "#/$defs/CConanLockV2Entry" + }, + { + "$ref": "#/$defs/CocoaPodfileLockEntry" + }, + { + "$ref": "#/$defs/DartPubspecLockEntry" + }, + { + "$ref": "#/$defs/DotnetDepsEntry" + }, + { + "$ref": "#/$defs/DotnetPortableExecutableEntry" + }, + { + "$ref": "#/$defs/DpkgDbEntry" + }, + { + "$ref": "#/$defs/ElixirMixLockEntry" + }, + { + "$ref": "#/$defs/ErlangRebarLockEntry" + }, + { + "$ref": "#/$defs/GoModuleBuildinfoEntry" + }, + { + "$ref": "#/$defs/GoModuleEntry" + }, + { + "$ref": "#/$defs/HaskellHackageStackEntry" + }, + { + "$ref": "#/$defs/HaskellHackageStackLockEntry" + }, + { + "$ref": "#/$defs/JavaArchive" + }, + { + "$ref": "#/$defs/JavascriptNpmPackage" + }, + { + "$ref": "#/$defs/JavascriptNpmPackageLockEntry" + }, + { + "$ref": "#/$defs/JavascriptYarnLockEntry" + }, + { + "$ref": "#/$defs/LinuxKernelArchive" + }, + { + "$ref": "#/$defs/LinuxKernelModule" + }, + { + "$ref": "#/$defs/MicrosoftKbPatch" + }, + { + "$ref": "#/$defs/NixStoreEntry" + }, + { + "$ref": "#/$defs/PhpComposerInstalledEntry" + }, + { + "$ref": "#/$defs/PhpComposerLockEntry" + }, + { + "$ref": "#/$defs/PortageDbEntry" + }, + { + "$ref": "#/$defs/PythonPackage" + }, + { + "$ref": "#/$defs/PythonPipRequirementsEntry" + }, + { + "$ref": "#/$defs/PythonPipfileLockEntry" + }, + { + "$ref": "#/$defs/PythonPoetryLockEntry" + }, + { + "$ref": "#/$defs/RDescription" + }, + { + "$ref": "#/$defs/RpmArchive" + }, + { + "$ref": "#/$defs/RpmDbEntry" + }, + { + "$ref": "#/$defs/RubyGemspec" + }, + { + "$ref": "#/$defs/RustCargoAuditEntry" + }, + { + "$ref": "#/$defs/RustCargoLockEntry" + }, + { + "$ref": "#/$defs/SwiftPackageManagerLockEntry" + }, + { + "$ref": "#/$defs/WordpressPluginEntry" + } + ] + } + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "foundBy", + "locations", + "licenses", + "language", + "cpes", + "purl" + ] + }, + "PhpComposerAuthors": { + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name" + ] + }, + "PhpComposerExternalReference": { + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "shasum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "type", + "url", + "reference" + ] + }, + "PhpComposerInstalledEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "dist": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "notification-url": { + "type": "string" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "$ref": "#/$defs/PhpComposerAuthors" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array" + }, + "time": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "dist" + ] + }, + "PhpComposerLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "dist": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "notification-url": { + "type": "string" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "$ref": "#/$defs/PhpComposerAuthors" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array" + }, + "time": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "dist" + ] + }, + "PortageDbEntry": { + "properties": { + "installedSize": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/PortageFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "installedSize", + "files" + ] + }, + "PortageFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PythonDirectURLOriginInfo": { + "properties": { + "url": { + "type": "string" + }, + "commitId": { + "type": "string" + }, + "vcs": { + "type": "string" + } + }, + "type": "object", + "required": [ + "url" + ] + }, + "PythonFileDigest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "PythonFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/PythonFileDigest" + }, + "size": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PythonPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorEmail": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/PythonFileRecord" + }, + "type": "array" + }, + "sitePackagesRootPath": { + "type": "string" + }, + "topLevelPackages": { + "items": { + "type": "string" + }, + "type": "array" + }, + "directUrlOrigin": { + "$ref": "#/$defs/PythonDirectURLOriginInfo" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "authorEmail", + "platform", + "sitePackagesRootPath" + ] + }, + "PythonPipRequirementsEntry": { + "properties": { + "name": { + "type": "string" + }, + "extras": { + "items": { + "type": "string" + }, + "type": "array" + }, + "versionConstraint": { + "type": "string" + }, + "url": { + "type": "string" + }, + "markers": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "versionConstraint" + ] + }, + "PythonPipfileLockEntry": { + "properties": { + "hashes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "index": { + "type": "string" + } + }, + "type": "object", + "required": [ + "hashes", + "index" + ] + }, + "PythonPoetryLockEntry": { + "properties": { + "index": { + "type": "string" + } + }, + "type": "object", + "required": [ + "index" + ] + }, + "RDescription": { + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "url": { + "items": { + "type": "string" + }, + "type": "array" + }, + "repository": { + "type": "string" + }, + "built": { + "type": "string" + }, + "needsCompilation": { + "type": "boolean" + }, + "imports": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "suggests": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "Relationship": { + "properties": { + "parent": { + "type": "string" + }, + "child": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": true + }, + "type": "object", + "required": [ + "parent", + "child", + "type" + ] + }, + "RpmArchive": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "epoch": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string" + }, + "release": { + "type": "string" + }, + "sourceRpm": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "vendor": { + "type": "string" + }, + "modularityLabel": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/RpmFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "vendor", + "files" + ] + }, + "RpmDbEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "epoch": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string" + }, + "release": { + "type": "string" + }, + "sourceRpm": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "vendor": { + "type": "string" + }, + "modularityLabel": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/RpmFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "vendor", + "files" + ] + }, + "RpmFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "mode": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "userName": { + "type": "string" + }, + "groupName": { + "type": "string" + }, + "flags": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "mode", + "size", + "digest", + "userName", + "groupName", + "flags" + ] + }, + "RubyGemspec": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "RustCargoAuditEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source" + ] + }, + "RustCargoLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "type": "string" + }, + "checksum": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "checksum", + "dependencies" + ] + }, + "Schema": { + "properties": { + "version": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "version", + "url" + ] + }, + "Source": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": true + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "metadata" + ] + }, + "SwiftPackageManagerLockEntry": { + "properties": { + "revision": { + "type": "string" + } + }, + "type": "object", + "required": [ + "revision" + ] + }, + "WordpressPluginEntry": { + "properties": { + "pluginInstallDirectory": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorUri": { + "type": "string" + } + }, + "type": "object", + "required": [ + "pluginInstallDirectory" + ] + }, + "cpes": { + "items": { + "$ref": "#/$defs/CPE" + }, + "type": "array" + }, + "licenses": { + "items": { + "$ref": "#/$defs/License" + }, + "type": "array" + } + } +} diff --git a/schema/json/schema-latest.json b/schema/json/schema-latest.json index cba3ff9855b2..1ecbc0db0247 100644 --- a/schema/json/schema-latest.json +++ b/schema/json/schema-latest.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "anchore.io/schema/syft/json/16.0.4/document", + "$id": "anchore.io/schema/syft/json/16.0.5/document", "$ref": "#/$defs/Document", "$defs": { "AlpmDbEntry": { @@ -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": { @@ -681,13 +708,28 @@ "format": { "type": "string" }, + "hasExports": { + "type": "boolean" + }, + "hasEntrypoint": { + "type": "boolean" + }, + "importedLibraries": { + "items": { + "type": "string" + }, + "type": "array" + }, "elfSecurityFeatures": { "$ref": "#/$defs/ELFSecurityFeatures" } }, "type": "object", "required": [ - "format" + "format", + "hasExports", + "hasEntrypoint", + "importedLibraries" ] }, "File": { @@ -1410,6 +1452,9 @@ { "$ref": "#/$defs/DpkgDbEntry" }, + { + "$ref": "#/$defs/ElfBinaryPackageNotes" + }, { "$ref": "#/$defs/ElixirMixLockEntry" }, diff --git a/syft/file/cataloger/executable/cataloger.go b/syft/file/cataloger/executable/cataloger.go index e75eb813f45e..182f340e1c5d 100644 --- a/syft/file/cataloger/executable/cataloger.go +++ b/syft/file/cataloger/executable/cataloger.go @@ -155,13 +155,25 @@ func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file data.Format = format - securityFeatures, err := findSecurityFeatures(format, reader) - if err != nil { - log.WithFields("error", err).Tracef("unable to determine security features for %q", loc.RealPath) - return nil, nil + switch format { + case file.ELF: + if err := findELFFeatures(&data, reader); err != nil { + log.WithFields("error", err).Tracef("unable to determine ELF features for %q", loc.RealPath) + } + case file.PE: + if err := findPEFeatures(&data, reader); err != nil { + log.WithFields("error", err).Tracef("unable to determine PE features for %q", loc.RealPath) + } + case file.MachO: + if err := findMachoFeatures(&data, reader); err != nil { + log.WithFields("error", err).Tracef("unable to determine Macho features for %q", loc.RealPath) + } } - data.SecurityFeatures = securityFeatures + // always allocate collections for presentation + if data.ImportedLibraries == nil { + data.ImportedLibraries = []string{} + } return &data, nil } @@ -230,18 +242,3 @@ func isPE(by []byte) bool { func isELF(by []byte) bool { return bytes.HasPrefix(by, []byte(elf.ELFMAG)) } - -func findSecurityFeatures(format file.ExecutableFormat, reader unionreader.UnionReader) (*file.ELFSecurityFeatures, error) { - // TODO: add support for PE and MachO - switch format { //nolint: gocritic - case file.ELF: - return findELFSecurityFeatures(reader) //nolint: gocritic - case file.PE: - // return findPESecurityFeatures(reader) - return nil, nil - case file.MachO: - // return findMachOSecurityFeatures(reader) - return nil, nil - } - return nil, fmt.Errorf("unsupported executable format: %q", format) -} diff --git a/syft/file/cataloger/executable/elf.go b/syft/file/cataloger/executable/elf.go index c1218255e39b..dec6abd34ed9 100644 --- a/syft/file/cataloger/executable/elf.go +++ b/syft/file/cataloger/executable/elf.go @@ -12,13 +12,33 @@ import ( "github.com/anchore/syft/syft/internal/unionreader" ) -func findELFSecurityFeatures(reader unionreader.UnionReader) (*file.ELFSecurityFeatures, error) { +func findELFFeatures(data *file.Executable, reader unionreader.UnionReader) error { f, err := elf.NewFile(reader) if err != nil { - return nil, nil + return err } - features := file.ELFSecurityFeatures{ + libs, err := f.ImportedLibraries() + if err != nil { + // TODO: known-unknowns + log.WithFields("error", err).Trace("unable to read imported libraries from elf file") + libs = nil + } + + if libs == nil { + libs = []string{} + } + + data.ImportedLibraries = libs + data.ELFSecurityFeatures = findELFSecurityFeatures(f) + data.HasEntrypoint = elfHasEntrypoint(f) + data.HasExports = elfHasExports(f) + + return nil +} + +func findELFSecurityFeatures(f *elf.File) *file.ELFSecurityFeatures { + return &file.ELFSecurityFeatures{ SymbolTableStripped: isElfSymbolTableStripped(f), StackCanary: checkElfStackCanary(f), NoExecutable: checkElfNXProtection(f), @@ -29,8 +49,6 @@ func findELFSecurityFeatures(reader unionreader.UnionReader) (*file.ELFSecurityF LlvmControlFlowIntegrity: checkLLVMControlFlowIntegrity(f), ClangFortifySource: checkClangFortifySource(f), } - - return &features, nil } func isElfSymbolTableStripped(file *elf.File) bool { @@ -219,3 +237,34 @@ func checkClangFortifySource(file *elf.File) *bool { } return boolRef(false) } + +func elfHasEntrypoint(f *elf.File) bool { + // this is akin to + // readelf -h ./path/to/bin | grep "Entry point address" + return f.Entry > 0 +} + +func elfHasExports(f *elf.File) bool { + // this is akin to: + // nm -D --defined-only ./path/to/bin | grep ' T \| W \| B ' + // where: + // T - symbol in the text section + // W - weak symbol that might be overwritten + // B - variable located in the uninitialized data section + // really anything that is not marked with 'U' (undefined) is considered an export. + symbols, err := f.DynamicSymbols() + if err != nil { + // TODO: known-unknowns? + return false + } + + for _, s := range symbols { + // check if the section is SHN_UNDEF, which is the "U" output type from "nm -D" meaning that the symbol + // is undefined, meaning it is not an export. Any entry that is not undefined is considered an export. + if s.Section != elf.SHN_UNDEF { + return true + } + } + + return false +} diff --git a/syft/file/cataloger/executable/elf_test.go b/syft/file/cataloger/executable/elf_test.go index 1ca484ff001e..454775891068 100644 --- a/syft/file/cataloger/executable/elf_test.go +++ b/syft/file/cataloger/executable/elf_test.go @@ -1,11 +1,13 @@ package executable import ( + "debug/elf" "os" "path/filepath" "testing" "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/anchore/syft/syft/file" @@ -16,7 +18,7 @@ func Test_findELFSecurityFeatures(t *testing.T) { readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader { t.Helper() - f, err := os.Open(filepath.Join("test-fixtures", fixture)) + f, err := os.Open(filepath.Join("test-fixtures/elf", fixture)) require.NoError(t, err) return f } @@ -26,7 +28,6 @@ func Test_findELFSecurityFeatures(t *testing.T) { fixture string want *file.ELFSecurityFeatures wantStripped bool - wantErr require.ErrorAssertionFunc }{ { name: "detect canary", @@ -145,14 +146,10 @@ func Test_findELFSecurityFeatures(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.wantErr == nil { - tt.wantErr = require.NoError - } - got, err := findELFSecurityFeatures(readerForFixture(t, tt.fixture)) - tt.wantErr(t, err) - if err != nil { - return - } + f, err := elf.NewFile(readerForFixture(t, tt.fixture)) + require.NoError(t, err) + + got := findELFSecurityFeatures(f) if d := cmp.Diff(tt.want, got); d != "" { t.Errorf("findELFSecurityFeatures() mismatch (-want +got):\n%s", d) @@ -160,3 +157,70 @@ func Test_findELFSecurityFeatures(t *testing.T) { }) } } + +func Test_elfHasEntrypoint(t *testing.T) { + + readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader { + t.Helper() + f, err := os.Open(filepath.Join("test-fixtures/shared-info", fixture)) + require.NoError(t, err) + return f + } + + tests := []struct { + name string + fixture string + want bool + }{ + { + name: "shared lib", + fixture: "bin/libhello.so", + want: false, + }, + { + name: "application", + fixture: "bin/hello_linux", + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := elf.NewFile(readerForFixture(t, tt.fixture)) + require.NoError(t, err) + assert.Equal(t, tt.want, elfHasEntrypoint(f)) + }) + } +} + +func Test_elfHasExports(t *testing.T) { + readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader { + t.Helper() + f, err := os.Open(filepath.Join("test-fixtures/shared-info", fixture)) + require.NoError(t, err) + return f + } + + tests := []struct { + name string + fixture string + want bool + }{ + { + name: "shared lib", + fixture: "bin/libhello.so", + want: true, + }, + { + name: "application", + fixture: "bin/hello_linux", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := elf.NewFile(readerForFixture(t, tt.fixture)) + require.NoError(t, err) + assert.Equal(t, tt.want, elfHasExports(f)) + }) + } +} diff --git a/syft/file/cataloger/executable/macho.go b/syft/file/cataloger/executable/macho.go new file mode 100644 index 000000000000..76d5d0ecea5b --- /dev/null +++ b/syft/file/cataloger/executable/macho.go @@ -0,0 +1,92 @@ +package executable + +import ( + "debug/macho" + + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/internal/unionreader" +) + +// source http://www.cilinder.be/docs/next/NeXTStep/3.3/nd/DevTools/14_MachO/MachO.htmld/index.html +const ( + machoNPExt uint8 = 0x10 /* N_PEXT: private external symbol bit */ + machoNExt uint8 = 0x01 /* N_EXT: external symbol bit, set for external symbols */ + // > #define LC_REQ_DYLD 0x80000000 + // > #define LC_MAIN (0x28|LC_REQ_DYLD) /* replacement for LC_UNIXTHREAD */ + lcMain = 0x28 | 0x80000000 +) + +func findMachoFeatures(data *file.Executable, reader unionreader.UnionReader) error { + // TODO: support security features + + // TODO: support multi-architecture binaries + f, err := macho.NewFile(reader) + if err != nil { + return err + } + + libs, err := f.ImportedLibraries() + if err != nil { + return err + } + + data.ImportedLibraries = libs + data.HasEntrypoint = machoHasEntrypoint(f) + data.HasExports = machoHasExports(f) + + return nil +} + +func machoHasEntrypoint(f *macho.File) bool { + // derived from struct entry_point_command found from which explicitly calls out LC_MAIN: + // https://opensource.apple.com/source/xnu/xnu-2050.18.24/EXTERNAL_HEADERS/mach-o/loader.h + // we need to look for both LC_MAIN and LC_UNIXTHREAD commands to determine if the file is an executable + // + // this is akin to: + // otool -l ./path/to/bin | grep -A4 LC_MAIN + // otool -l ./path/to/bin | grep -A4 LC_UNIXTHREAD + for _, l := range f.Loads { + data := l.Raw() + cmd := f.ByteOrder.Uint32(data) + + if macho.LoadCmd(cmd) == macho.LoadCmdUnixThread || macho.LoadCmd(cmd) == lcMain { + return true + } + } + return false +} + +func machoHasExports(f *macho.File) bool { + for _, sym := range f.Symtab.Syms { + // look for symbols that are: + // - not private and are external + // - do not have an N_TYPE value of N_UNDF (undefined symbol) + // + // here's the bit layout for the n_type field: + // 0000 0000 + // ─┬─│ ─┬─│ + // │ │ │ └─ N_EXT (external symbol) + // │ │ └─ N_TYPE (N_UNDF, N_ABS, N_SECT, N_PBUD, N_INDR) + // │ └─ N_PEXT (private external symbol) + // └─ N_STAB (debugging symbol) + // + isExternal := sym.Type&machoNExt == machoNExt + isPrivate := sym.Type&machoNPExt == machoNPExt + nTypeIsUndefined := sym.Type&0x0e == 0 + + if isExternal && !isPrivate { + if sym.Name == "_main" || sym.Name == "__mh_execute_header" { + // ...however there are some symbols that are not exported but are still important + // for debugging or as an entrypoint, so we need to explicitly check for them + continue + } + if nTypeIsUndefined { + continue + } + // we have a symbol that is not private and is external + // and is not undefined, so it is an export + return true + } + } + return false +} diff --git a/syft/file/cataloger/executable/macho_test.go b/syft/file/cataloger/executable/macho_test.go new file mode 100644 index 000000000000..22b39940d8e9 --- /dev/null +++ b/syft/file/cataloger/executable/macho_test.go @@ -0,0 +1,80 @@ +package executable + +import ( + "debug/macho" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/syft/syft/internal/unionreader" +) + +func Test_machoHasEntrypoint(t *testing.T) { + + readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader { + t.Helper() + f, err := os.Open(filepath.Join("test-fixtures/shared-info", fixture)) + require.NoError(t, err) + return f + } + + tests := []struct { + name string + fixture string + want bool + }{ + { + name: "shared lib", + fixture: "bin/libhello.dylib", + want: false, + }, + { + name: "application", + fixture: "bin/hello_mac", + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := macho.NewFile(readerForFixture(t, tt.fixture)) + require.NoError(t, err) + assert.Equal(t, tt.want, machoHasEntrypoint(f)) + }) + } +} + +func Test_machoHasExports(t *testing.T) { + readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader { + t.Helper() + f, err := os.Open(filepath.Join("test-fixtures/shared-info", fixture)) + require.NoError(t, err) + return f + } + + tests := []struct { + name string + fixture string + want bool + }{ + { + name: "shared lib", + fixture: "bin/libhello.dylib", + want: true, + }, + { + name: "application", + fixture: "bin/hello_mac", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := macho.NewFile(readerForFixture(t, tt.fixture)) + require.NoError(t, err) + assert.Equal(t, tt.want, machoHasExports(f)) + }) + } +} diff --git a/syft/file/cataloger/executable/pe.go b/syft/file/cataloger/executable/pe.go new file mode 100644 index 000000000000..b070c3c8402a --- /dev/null +++ b/syft/file/cataloger/executable/pe.go @@ -0,0 +1,84 @@ +package executable + +import ( + "debug/pe" + + "github.com/scylladb/go-set/strset" + + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/internal/unionreader" +) + +func findPEFeatures(data *file.Executable, reader unionreader.UnionReader) error { + // TODO: support security features + + f, err := pe.NewFile(reader) + if err != nil { + return err + } + + libs, err := f.ImportedLibraries() + if err != nil { + return err + } + + data.ImportedLibraries = libs + data.HasEntrypoint = peHasEntrypoint(f) + data.HasExports = peHasExports(f) + + return nil +} + +var ( + windowsExeEntrypoints = strset.New("main", "WinMain", "wWinMain") + windowsDllEntrypoints = strset.New("DllMain", "_DllMainCRTStartup@12", "CRT_INIT") +) + +func peHasEntrypoint(f *pe.File) bool { + // DLLs can have entrypoints, but they are not "executables" in the traditional sense, + // but instead point to an initialization function (DLLMain). + // The PE format does not require an entrypoint, so it is possible to not have one, however, + // the microsoft C runtime does: https://learn.microsoft.com/en-US/troubleshoot/developer/visualstudio/cpp/libraries/use-c-run-time + // + // > When building a DLL which uses any of the C Run-time libraries, in order to ensure that the CRT is properly initialized, either + // > 1. the initialization function must be named DllMain() and the entry point must be specified with the linker option -entry:_DllMainCRTStartup@12 - or - + // > 2. the DLL's entry point must explicitly call CRT_INIT() on process attach and process detach + // + // This isn't really helpful from a user perspective when it comes to indicating if there is an entrypoint or not + // since it will always effectively be true for DLLs! All DLLs and Executables (aka "modules") have a single + // entrypoint, _GetPEImageBase, but we're more interested in the logical idea of an entrypoint. + // See https://learn.microsoft.com/en-us/windows/win32/psapi/module-information for more details. + + var hasLibEntrypoint, hasExeEntrypoint bool + for _, s := range f.Symbols { + if windowsExeEntrypoints.Has(s.Name) { + hasExeEntrypoint = true + } + if windowsDllEntrypoints.Has(s.Name) { + hasLibEntrypoint = true + } + } + + switch v := f.OptionalHeader.(type) { + case *pe.OptionalHeader32: + return v.AddressOfEntryPoint > 0 && !hasLibEntrypoint && hasExeEntrypoint + case *pe.OptionalHeader64: + return v.AddressOfEntryPoint > 0 && !hasLibEntrypoint && hasExeEntrypoint + } + return false +} + +func peHasExports(f *pe.File) bool { + if f.OptionalHeader == nil { + return false + } + + switch v := f.OptionalHeader.(type) { + case *pe.OptionalHeader32: + return v.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXPORT].Size > 0 + case *pe.OptionalHeader64: + return v.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXPORT].Size > 0 + } + + return false +} diff --git a/syft/file/cataloger/executable/pe_test.go b/syft/file/cataloger/executable/pe_test.go new file mode 100644 index 000000000000..59ea5bc47aeb --- /dev/null +++ b/syft/file/cataloger/executable/pe_test.go @@ -0,0 +1,80 @@ +package executable + +import ( + "debug/pe" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/syft/syft/internal/unionreader" +) + +func Test_peHasEntrypoint(t *testing.T) { + + readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader { + t.Helper() + f, err := os.Open(filepath.Join("test-fixtures/shared-info", fixture)) + require.NoError(t, err) + return f + } + + tests := []struct { + name string + fixture string + want bool + }{ + { + name: "shared lib", + fixture: "bin/hello.dll", // though this is a shared lib, it has an entrypoint (DLLMain) + want: false, + }, + { + name: "application", + fixture: "bin/hello.exe", + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := pe.NewFile(readerForFixture(t, tt.fixture)) + require.NoError(t, err) + assert.Equal(t, tt.want, peHasEntrypoint(f)) + }) + } +} + +func Test_peHasExports(t *testing.T) { + readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader { + t.Helper() + f, err := os.Open(filepath.Join("test-fixtures/shared-info", fixture)) + require.NoError(t, err) + return f + } + + tests := []struct { + name string + fixture string + want bool + }{ + { + name: "shared lib", + fixture: "bin/hello.dll", + want: true, + }, + { + name: "application", + fixture: "bin/hello.exe", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := pe.NewFile(readerForFixture(t, tt.fixture)) + require.NoError(t, err) + assert.Equal(t, tt.want, peHasExports(f)) + }) + } +} diff --git a/syft/file/cataloger/executable/test-fixtures/elf/.gitignore b/syft/file/cataloger/executable/test-fixtures/elf/.gitignore new file mode 100644 index 000000000000..87697d292d28 --- /dev/null +++ b/syft/file/cataloger/executable/test-fixtures/elf/.gitignore @@ -0,0 +1,3 @@ +../bin +actual_verify +Dockerfile.sha256 \ No newline at end of file diff --git a/syft/file/cataloger/executable/test-fixtures/Dockerfile b/syft/file/cataloger/executable/test-fixtures/elf/Dockerfile similarity index 100% rename from syft/file/cataloger/executable/test-fixtures/Dockerfile rename to syft/file/cataloger/executable/test-fixtures/elf/Dockerfile diff --git a/syft/file/cataloger/executable/test-fixtures/Makefile b/syft/file/cataloger/executable/test-fixtures/elf/Makefile similarity index 100% rename from syft/file/cataloger/executable/test-fixtures/Makefile rename to syft/file/cataloger/executable/test-fixtures/elf/Makefile diff --git a/syft/file/cataloger/executable/test-fixtures/differ/__main__.py b/syft/file/cataloger/executable/test-fixtures/elf/differ/__main__.py similarity index 100% rename from syft/file/cataloger/executable/test-fixtures/differ/__main__.py rename to syft/file/cataloger/executable/test-fixtures/elf/differ/__main__.py diff --git a/syft/file/cataloger/executable/test-fixtures/expected_verify b/syft/file/cataloger/executable/test-fixtures/elf/expected_verify similarity index 100% rename from syft/file/cataloger/executable/test-fixtures/expected_verify rename to syft/file/cataloger/executable/test-fixtures/elf/expected_verify diff --git a/syft/file/cataloger/executable/test-fixtures/project/Makefile b/syft/file/cataloger/executable/test-fixtures/elf/project/Makefile similarity index 100% rename from syft/file/cataloger/executable/test-fixtures/project/Makefile rename to syft/file/cataloger/executable/test-fixtures/elf/project/Makefile diff --git a/syft/file/cataloger/executable/test-fixtures/project/lib.c b/syft/file/cataloger/executable/test-fixtures/elf/project/lib.c similarity index 100% rename from syft/file/cataloger/executable/test-fixtures/project/lib.c rename to syft/file/cataloger/executable/test-fixtures/elf/project/lib.c diff --git a/syft/file/cataloger/executable/test-fixtures/project/lib.h b/syft/file/cataloger/executable/test-fixtures/elf/project/lib.h similarity index 100% rename from syft/file/cataloger/executable/test-fixtures/project/lib.h rename to syft/file/cataloger/executable/test-fixtures/elf/project/lib.h diff --git a/syft/file/cataloger/executable/test-fixtures/project/main.c b/syft/file/cataloger/executable/test-fixtures/elf/project/main.c similarity index 100% rename from syft/file/cataloger/executable/test-fixtures/project/main.c rename to syft/file/cataloger/executable/test-fixtures/elf/project/main.c diff --git a/syft/file/cataloger/executable/test-fixtures/shared-info/.gitignore b/syft/file/cataloger/executable/test-fixtures/shared-info/.gitignore new file mode 100644 index 000000000000..c5e82d74585d --- /dev/null +++ b/syft/file/cataloger/executable/test-fixtures/shared-info/.gitignore @@ -0,0 +1 @@ +bin \ No newline at end of file diff --git a/syft/file/cataloger/executable/test-fixtures/shared-info/Dockerfile b/syft/file/cataloger/executable/test-fixtures/shared-info/Dockerfile new file mode 100644 index 000000000000..da598949e956 --- /dev/null +++ b/syft/file/cataloger/executable/test-fixtures/shared-info/Dockerfile @@ -0,0 +1,16 @@ +# syntax=docker/dockerfile:1 + +ARG OSXCROSS_VERSION=13.1 +ARG BUILDPLATFORM=linux/amd64 +FROM --platform=$BUILDPLATFORM crazymax/osxcross:${OSXCROSS_VERSION}-ubuntu AS osxcross + +FROM --platform=$BUILDPLATFORM ubuntu:22.04 + +RUN apt update -y && apt install -y \ + curl wget \ + make gcc clang \ + git mingw-w64 + +COPY --from=osxcross /osxcross /osxcross +ENV PATH="/osxcross/bin:$PATH" +ENV LD_LIBRARY_PATH="/osxcross/lib:$LD_LIBRARY_PATH" diff --git a/syft/file/cataloger/executable/test-fixtures/shared-info/Makefile b/syft/file/cataloger/executable/test-fixtures/shared-info/Makefile new file mode 100644 index 000000000000..a3d5959c3584 --- /dev/null +++ b/syft/file/cataloger/executable/test-fixtures/shared-info/Makefile @@ -0,0 +1,25 @@ +BIN=./bin +TOOL_IMAGE=localhost/syft-shared-info-build-tools:latest +VERIFY_FILE=actual_verify + +all: build +tools-check: + @sha256sum -c Dockerfile.sha256 || (echo "Tools Dockerfile has changed" && exit 1) + +tools: + @(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || (docker build -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256) + +build: tools + mkdir -p $(BIN) + docker run --platform linux/amd64 -i -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) make + +debug: + docker run --platform linux/amd64 -i --rm -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) bash + +cache.fingerprint: + @find project Dockerfile Makefile -type f -exec md5sum {} + | awk '{print $1}' | sort | tee cache.fingerprint + +clean: + rm -f $(BIN)/* + +.PHONY: build verify debug build-image build-bins clean dockerfile-check cache.fingerprint diff --git a/syft/file/cataloger/executable/test-fixtures/shared-info/project/Makefile b/syft/file/cataloger/executable/test-fixtures/shared-info/project/Makefile new file mode 100644 index 000000000000..1a2eefb9fc90 --- /dev/null +++ b/syft/file/cataloger/executable/test-fixtures/shared-info/project/Makefile @@ -0,0 +1,10 @@ +# invoke all make files in subdirectories +.PHONY: all hello libhello + +all: hello libhello + +hello: + $(MAKE) -C hello + +libhello: + $(MAKE) -C libhello diff --git a/syft/file/cataloger/executable/test-fixtures/shared-info/project/hello/Makefile b/syft/file/cataloger/executable/test-fixtures/shared-info/project/hello/Makefile new file mode 100644 index 000000000000..6a0bbbf4e24f --- /dev/null +++ b/syft/file/cataloger/executable/test-fixtures/shared-info/project/hello/Makefile @@ -0,0 +1,23 @@ +.PHONY: all linux windows mac + +BIN=../../bin + +all: $(BIN)/hello_linux $(BIN)/hello.exe $(BIN)/hello_mac + +linux: $(BIN)/libhello.so + +windows: $(BIN)/libhello.dll + +mac: $(BIN)/libhello.dylib + +$(BIN)/hello_linux: + gcc hello.c -o $(BIN)/hello_linux + +$(BIN)/hello.exe: + x86_64-w64-mingw32-gcc hello.c -o $(BIN)/hello.exe + +$(BIN)/hello_mac: + o64-clang hello.c -o $(BIN)/hello_mac + +clean: + rm -f $(BIN)/hello_linux $(BIN)/hello.exe $(BIN)/hello_mac diff --git a/syft/file/cataloger/executable/test-fixtures/shared-info/project/hello/hello.c b/syft/file/cataloger/executable/test-fixtures/shared-info/project/hello/hello.c new file mode 100644 index 000000000000..f26b97c98dbd --- /dev/null +++ b/syft/file/cataloger/executable/test-fixtures/shared-info/project/hello/hello.c @@ -0,0 +1,6 @@ +#include + +int main() { + printf("Hello, World!\n"); + return 0; +} diff --git a/syft/file/cataloger/executable/test-fixtures/shared-info/project/libhello/Makefile b/syft/file/cataloger/executable/test-fixtures/shared-info/project/libhello/Makefile new file mode 100644 index 000000000000..e9b3860bb7b4 --- /dev/null +++ b/syft/file/cataloger/executable/test-fixtures/shared-info/project/libhello/Makefile @@ -0,0 +1,23 @@ +.PHONY: all linux windows mac + +BIN=../../bin + +all: $(BIN)/libhello.so $(BIN)/libhello.dll $(BIN)/libhello.dylib + +linux: $(BIN)/libhello.so + +windows: $(BIN)/libhello.dll + +mac: $(BIN)/libhello.dylib + +$(BIN)/libhello.so: + gcc -shared -fPIC -o $(BIN)/libhello.so hello.c + +$(BIN)/libhello.dll: + x86_64-w64-mingw32-gcc -shared -o $(BIN)/hello.dll hello.c -Wl,--out-implib,$(BIN)/libhello.a + +$(BIN)/libhello.dylib: + o64-clang -dynamiclib -o $(BIN)/libhello.dylib hello.c + +clean: + rm -f $(BIN)/libhello.so $(BIN)/hello.dll $(BIN)/libhello.dylib $(BIN)/libhello.a diff --git a/syft/file/cataloger/executable/test-fixtures/shared-info/project/libhello/hello.c b/syft/file/cataloger/executable/test-fixtures/shared-info/project/libhello/hello.c new file mode 100644 index 000000000000..7d258365f1b3 --- /dev/null +++ b/syft/file/cataloger/executable/test-fixtures/shared-info/project/libhello/hello.c @@ -0,0 +1,5 @@ +#include + +void hello() { + printf("Hello from shared library!\n"); +} \ No newline at end of file diff --git a/syft/file/cataloger/executable/test-fixtures/shared-info/project/libhello/hello.h b/syft/file/cataloger/executable/test-fixtures/shared-info/project/libhello/hello.h new file mode 100644 index 000000000000..aaa8cddb3383 --- /dev/null +++ b/syft/file/cataloger/executable/test-fixtures/shared-info/project/libhello/hello.h @@ -0,0 +1,6 @@ +#ifndef HELLO_H +#define HELLO_H + +void hello(); + +#endif \ No newline at end of file diff --git a/syft/file/executable.go b/syft/file/executable.go index d9d3ad0871c6..dc0990b5d051 100644 --- a/syft/file/executable.go +++ b/syft/file/executable.go @@ -19,7 +19,10 @@ type Executable struct { // Format denotes either ELF, Mach-O, or PE Format ExecutableFormat `json:"format" yaml:"format" mapstructure:"format"` - SecurityFeatures *ELFSecurityFeatures `json:"elfSecurityFeatures,omitempty" yaml:"elfSecurityFeatures" mapstructure:"elfSecurityFeatures"` + HasExports bool `json:"hasExports" yaml:"hasExports" mapstructure:"hasExports"` + HasEntrypoint bool `json:"hasEntrypoint" yaml:"hasEntrypoint" mapstructure:"hasEntrypoint"` + ImportedLibraries []string `json:"importedLibraries" yaml:"importedLibraries" mapstructure:"importedLibraries"` + ELFSecurityFeatures *ELFSecurityFeatures `json:"elfSecurityFeatures,omitempty" yaml:"elfSecurityFeatures" mapstructure:"elfSecurityFeatures"` } type ELFSecurityFeatures struct { diff --git a/syft/format/syftjson/decoder_test.go b/syft/format/syftjson/decoder_test.go index ec403b3729af..daa019dc4fb1 100644 --- a/syft/format/syftjson/decoder_test.go +++ b/syft/format/syftjson/decoder_test.go @@ -224,7 +224,7 @@ func Test_encodeDecodeFileMetadata(t *testing.T) { Executables: map[file.Coordinates]file.Executable{ c: { Format: file.ELF, - SecurityFeatures: &file.ELFSecurityFeatures{ + ELFSecurityFeatures: &file.ELFSecurityFeatures{ SymbolTableStripped: false, StackCanary: boolRef(true), NoExecutable: false, diff --git a/syft/format/syftjson/to_syft_model_test.go b/syft/format/syftjson/to_syft_model_test.go index c8ef3b6b4f1e..5559db2953ca 100644 --- a/syft/format/syftjson/to_syft_model_test.go +++ b/syft/format/syftjson/to_syft_model_test.go @@ -288,7 +288,7 @@ func Test_toSyftFiles(t *testing.T) { }, Executable: &file.Executable{ Format: file.ELF, - SecurityFeatures: &file.ELFSecurityFeatures{ + ELFSecurityFeatures: &file.ELFSecurityFeatures{ SymbolTableStripped: false, StackCanary: boolRef(true), NoExecutable: false, @@ -329,7 +329,7 @@ func Test_toSyftFiles(t *testing.T) { Executables: map[file.Coordinates]file.Executable{ coord: { Format: file.ELF, - SecurityFeatures: &file.ELFSecurityFeatures{ + ELFSecurityFeatures: &file.ELFSecurityFeatures{ SymbolTableStripped: false, StackCanary: boolRef(true), NoExecutable: false, diff --git a/syft/internal/packagemetadata/generated.go b/syft/internal/packagemetadata/generated.go index d7ba10159d55..8d09d7d8b40b 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 805d197ea065..c738202fc811 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 ddbe4cfff705..2779fc9861be 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 e42f456ade8b..e4cb9cd67ba8 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 885374260722..d5e6a4418ab6 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 f3326e2b6b40..a2ed4ab6de14 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 000000000000..fc73016755ff --- /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 000000000000..abf6c7558242 --- /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 000000000000..4909ac8a85eb --- /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 000000000000..bb47e69b0e49 --- /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 000000000000..a5efa56eb3fd --- /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 000000000000..9da59af37b5b --- /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 000000000000..d4193a609611 --- /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 000000000000..e73f603f2393 --- /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 000000000000..58a6e10506a4 --- /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 000000000000..b53f429a381b --- /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 000000000000..9da59af37b5b --- /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 000000000000..d4193a609611 --- /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 000000000000..0d115202ae93 --- /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 000000000000..58a6e10506a4 --- /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 000000000000..669e84a3e59e --- /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 000000000000..f611ecbd9d3c --- /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 000000000000..8124389f05aa --- /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 000000000000..719cb8dbadd1 --- /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 000000000000..e72541d7bf8d --- /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/cataloger/dotnet/package_test.go b/syft/pkg/cataloger/dotnet/package_test.go index 7f2acbfbd47b..6ff473a89a61 100644 --- a/syft/pkg/cataloger/dotnet/package_test.go +++ b/syft/pkg/cataloger/dotnet/package_test.go @@ -1,8 +1,9 @@ package dotnet import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func Test_getDepsJSONFilePrefix(t *testing.T) { diff --git a/syft/pkg/language.go b/syft/pkg/language.go index b0e3641fb55f..347c225083dc 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