package binary import ( "context" "errors" "flag" "fmt" "io" "slices" "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/anchore/packageurl-go" "github.com/anchore/stereoscope/pkg/imagetest" "github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/binary/internal/manager/testutil" "github.com/anchore/syft/syft/pkg/cataloger/internal/binutils" "github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source/directorysource" "github.com/anchore/syft/syft/source/stereoscopesource" ) var mustUseOriginalBinaries = flag.Bool("must-use-original-binaries", false, "force the use of binaries for testing (instead of snippets)") func Test_Cataloger_PositiveCases(t *testing.T) { tests := []struct { name string // logicalFixture is the logical path to the full binary or snippet. This is relative to the testdata/classifiers/snippets // or testdata/classifiers/bin directory . Snippets are searched for first, and if not found, then existing binaries are // used. If no binary or snippet is found the test will fail. If '-must-use-original-binaries' is used the only // full binaries are tested (no snippets), and if no binary is found the test will be skipped. logicalFixture string expected pkg.Package }{ { logicalFixture: "arangodb/3.11.8/linux-amd64", expected: pkg.Package{ Name: "arangodb", Version: "3.11.8", Type: "binary", PURL: "pkg:generic/arangodb@3.11.8", Locations: locations("arangosh"), Metadata: metadata("arangodb-binary"), }, }, { logicalFixture: "arangodb/3.12.0-2/linux-amd64", expected: pkg.Package{ Name: "arangodb", Version: "3.12.0-2", Type: "binary", PURL: "pkg:generic/arangodb@3.12.0-2", Locations: locations("arangosh"), Metadata: metadata("arangodb-binary"), }, }, { logicalFixture: "arangodb/3.12.5/linux-amd64", expected: pkg.Package{ Name: "arangodb", Version: "3.12.5", Type: "binary", PURL: "pkg:generic/arangodb@3.12.5", Locations: locations("arangosh"), Metadata: metadata("arangodb-binary"), }, }, { logicalFixture: "arangodb/3.12.5-2/linux-amd64", expected: pkg.Package{ Name: "arangodb", Version: "3.12.5-2", Type: "binary", PURL: "pkg:generic/arangodb@3.12.5-2", Locations: locations("arangosh"), Metadata: metadata("arangodb-binary"), }, }, { logicalFixture: "postgres/15beta4/linux-amd64", expected: pkg.Package{ Name: "postgresql", Version: "15beta4", Type: "binary", PURL: "pkg:generic/postgresql@15beta4", Locations: locations("postgres"), Metadata: metadata("postgresql-binary"), }, }, { logicalFixture: "postgres/15.1/linux-amd64", expected: pkg.Package{ Name: "postgresql", Version: "15.1", Type: "binary", PURL: "pkg:generic/postgresql@15.1", Locations: locations("postgres"), Metadata: metadata("postgresql-binary"), }, }, { logicalFixture: "postgres/9.6.24/linux-amd64", expected: pkg.Package{ Name: "postgresql", Version: "9.6.24", Type: "binary", PURL: "pkg:generic/postgresql@9.6.24", Locations: locations("postgres"), Metadata: metadata("postgresql-binary"), }, }, { // TODO: find original binary... // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo logicalFixture: "postgres/9.5alpha1/linux-amd64", expected: pkg.Package{ Name: "postgresql", Version: "9.5alpha1", Type: "binary", PURL: "pkg:generic/postgresql@9.5alpha1", Locations: locations("postgres"), Metadata: metadata("postgresql-binary"), }, }, { logicalFixture: "mysql/8.0.34/linux-amd64", expected: pkg.Package{ Name: "mysql", Version: "8.0.34", Type: "binary", PURL: "pkg:generic/mysql@8.0.34", Locations: locations("mysql"), Metadata: metadata("mysql-binary"), }, }, { logicalFixture: "mysql/8.0.37/linux-amd64", expected: pkg.Package{ Name: "mysql", Version: "8.0.37", Type: "binary", PURL: "pkg:generic/mysql@8.0.37", Locations: locations("mysql"), Metadata: metadata("mysql-binary"), }, }, { logicalFixture: "percona-server/8.0.35/linux-amd64", expected: pkg.Package{ Name: "percona-server", Version: "8.0.35", Type: "binary", PURL: "pkg:generic/percona-server@8.0.35", Locations: locations("mysql"), Metadata: metadata("mysql-binary"), }, }, { logicalFixture: "percona-xtradb-cluster/8.0.34/linux-amd64", expected: pkg.Package{ Name: "percona-xtradb-cluster", Version: "8.0.34", Type: "binary", PURL: "pkg:generic/percona-xtradb-cluster@8.0.34", Locations: locations("mysql"), Metadata: metadata("mysql-binary"), }, }, { logicalFixture: "percona-xtrabackup/8.0.35/linux-amd64", expected: pkg.Package{ Name: "percona-xtrabackup", Version: "8.0.35", Type: "binary", PURL: "pkg:generic/percona-xtrabackup@8.0.35", Locations: locations("xtrabackup"), Metadata: metadata("xtrabackup-binary"), }, }, { logicalFixture: "mysql/5.6.51/linux-amd64", expected: pkg.Package{ Name: "mysql", Version: "5.6.51", Type: "binary", PURL: "pkg:generic/mysql@5.6.51", Locations: locations("mysql"), Metadata: metadata("mysql-binary"), }, }, { logicalFixture: "mariadb/10.6.15/linux-amd64", expected: pkg.Package{ Name: "mariadb", Version: "10.6.15", Type: "binary", PURL: "pkg:generic/mariadb@10.6.15", Locations: locations("mariadb"), Metadata: metadata("mariadb-binary"), }, }, { logicalFixture: "mongodb/8.0.17/linux-amd64", expected: pkg.Package{ Name: "mongodb", Version: "8.0.17", Type: "binary", PURL: "pkg:generic/mongodb@8.0.17", Locations: locations("mongod"), Metadata: metadata("mongodb-binary"), }, }, { logicalFixture: "mongodb/7.0.28/linux-amd64", expected: pkg.Package{ Name: "mongodb", Version: "7.0.28", Type: "binary", PURL: "pkg:generic/mongodb@7.0.28", Locations: locations("mongod"), Metadata: metadata("mongodb-binary"), }, }, { logicalFixture: "mongodb/6.0.27/linux-amd64", expected: pkg.Package{ Name: "mongodb", Version: "6.0.27", Type: "binary", PURL: "pkg:generic/mongodb@6.0.27", Locations: locations("mongod"), Metadata: metadata("mongodb-binary"), }, }, { logicalFixture: "mongodb/5.0.32/linux-amd64", expected: pkg.Package{ Name: "mongodb", Version: "5.0.32", Type: "binary", PURL: "pkg:generic/mongodb@5.0.32", Locations: locations("mongod"), Metadata: metadata("mongodb-binary"), }, }, { logicalFixture: "mongodb/4.4.30/linux-amd64", expected: pkg.Package{ Name: "mongodb", Version: "4.4.30", Type: "binary", PURL: "pkg:generic/mongodb@4.4.30", Locations: locations("mongod"), Metadata: metadata("mongodb-binary"), }, }, { logicalFixture: "traefik/1.7.34/linux-amd64", expected: pkg.Package{ Name: "traefik", Version: "1.7.34", Type: "binary", PURL: "pkg:generic/traefik@1.7.34", Locations: locations("traefik"), Metadata: metadata("traefik-binary"), }, }, { logicalFixture: "traefik/2.9.6/linux-amd64", expected: pkg.Package{ Name: "traefik", Version: "2.9.6", Type: "binary", PURL: "pkg:generic/traefik@2.9.6", Locations: locations("traefik"), Metadata: metadata("traefik-binary"), }, }, { logicalFixture: "traefik/2.10.7/linux-amd64", expected: pkg.Package{ Name: "traefik", Version: "2.10.7", Type: "binary", PURL: "pkg:generic/traefik@2.10.7", Locations: locations("traefik"), Metadata: metadata("traefik-binary"), }, }, { logicalFixture: "traefik/3.0.4/linux-riscv64", expected: pkg.Package{ Name: "traefik", Version: "3.0.4", Type: "binary", PURL: "pkg:generic/traefik@3.0.4", Locations: locations("traefik"), Metadata: metadata("traefik-binary"), }, }, { logicalFixture: "traefik/3.6.5/linux-amd64", expected: pkg.Package{ Name: "traefik", Version: "3.6.5", Type: "binary", PURL: "pkg:generic/traefik@3.6.5", Locations: locations("traefik"), Metadata: metadata("traefik-binary"), }, }, { logicalFixture: "memcached/1.6.18/linux-amd64", expected: pkg.Package{ Name: "memcached", Version: "1.6.18", Type: "binary", PURL: "pkg:generic/memcached@1.6.18", Locations: locations("memcached"), Metadata: metadata("memcached-binary"), }, }, { logicalFixture: "httpd/2.4.54/linux-amd64", expected: pkg.Package{ Name: "httpd", Version: "2.4.54", Type: "binary", PURL: "pkg:generic/httpd@2.4.54", Locations: locations("httpd"), Metadata: metadata("httpd-binary"), }, }, { // TODO: original binary is different than whats in config.yaml // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo logicalFixture: "perl/5.12.5/linux-amd64", expected: pkg.Package{ Name: "perl", Version: "5.12.5", Type: "binary", PURL: "pkg:generic/perl@5.12.5", Locations: locations("perl"), Metadata: metadata("perl-binary"), }, }, { // TODO: original binary is different than whats in config.yaml // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo logicalFixture: "perl/5.20.0/linux-amd64", expected: pkg.Package{ Name: "perl", Version: "5.20.0", Type: "binary", PURL: "pkg:generic/perl@5.20.0", Locations: locations("perl"), Metadata: metadata("perl-binary"), }, }, { // TODO: original binary is different than whats in config.yaml // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo logicalFixture: "perl/5.37.8/linux-amd64", expected: pkg.Package{ Name: "perl", Version: "5.37.8", Type: "binary", PURL: "pkg:generic/perl@5.37.8", Locations: locations("perl"), Metadata: metadata("perl-binary"), }, }, { logicalFixture: "haproxy/1.5.14/linux-amd64", expected: pkg.Package{ Name: "haproxy", Version: "1.5.14", Type: "binary", PURL: "pkg:generic/haproxy@1.5.14", Locations: locations("haproxy"), Metadata: metadata("haproxy-binary"), }, }, { logicalFixture: "haproxy/1.8.22/linux-amd64", expected: pkg.Package{ Name: "haproxy", Version: "1.8.22", Type: "binary", PURL: "pkg:generic/haproxy@1.8.22", Locations: locations("haproxy"), Metadata: metadata("haproxy-binary"), }, }, { logicalFixture: "haproxy/2.0.0/linux-amd64", expected: pkg.Package{ Name: "haproxy", Version: "2.0.0", Type: "binary", PURL: "pkg:generic/haproxy@2.0.0", Locations: locations("haproxy"), Metadata: metadata("haproxy-binary"), }, }, { logicalFixture: "haproxy/2.7.3/linux-amd64", expected: pkg.Package{ Name: "haproxy", Version: "2.7.3", Type: "binary", PURL: "pkg:generic/haproxy@2.7.3", Locations: locations("haproxy"), Metadata: metadata("haproxy-binary"), }, }, { logicalFixture: "haproxy/3.1-dev0/linux-amd64", expected: pkg.Package{ Name: "haproxy", Version: "3.1-dev0", Type: "binary", PURL: "pkg:generic/haproxy@3.1-dev0", Locations: locations("haproxy"), Metadata: metadata("haproxy-binary"), }, }, { logicalFixture: "helm/3.11.1/linux-amd64", expected: pkg.Package{ Name: "helm", Version: "3.11.1", Type: "binary", PURL: "pkg:golang/helm.sh/helm@3.11.1", Locations: locations("helm"), Metadata: metadata("helm"), }, }, { logicalFixture: "helm/3.10.3/linux-amd64", expected: pkg.Package{ Name: "helm", Version: "3.10.3", Type: "binary", PURL: "pkg:golang/helm.sh/helm@3.10.3", Locations: locations("helm"), Metadata: metadata("helm"), }, }, { // note: dynamic (non-snippet) test case logicalFixture: "redis-server/2.8.23/linux-amd64", expected: pkg.Package{ Name: "redis", Version: "2.8.23", Type: "binary", PURL: "pkg:generic/redis@2.8.23", Locations: locations("redis-server"), Metadata: metadata("redis-binary"), }, }, { // note: dynamic (non-snippet) test case logicalFixture: "redis-server/4.0.11/linux-amd64", expected: pkg.Package{ Name: "redis", Version: "4.0.11", Type: "binary", PURL: "pkg:generic/redis@4.0.11", Locations: locations("redis-server"), Metadata: metadata("redis-binary"), }, }, { logicalFixture: "redis-server/5.0.0/linux-amd64", expected: pkg.Package{ Name: "redis", Version: "5.0.0", Type: "binary", PURL: "pkg:generic/redis@5.0.0", Locations: locations("redis-server"), Metadata: metadata("redis-binary"), }, }, { logicalFixture: "redis-server/6.0.16/linux-amd64", expected: pkg.Package{ Name: "redis", Version: "6.0.16", Type: "binary", PURL: "pkg:generic/redis@6.0.16", Locations: locations("redis-server"), Metadata: metadata("redis-binary"), }, }, { logicalFixture: "redis-server/7.0.0/linux-amd64", expected: pkg.Package{ Name: "redis", Version: "7.0.0", Type: "binary", PURL: "pkg:generic/redis@7.0.0", Locations: locations("redis-server"), Metadata: metadata("redis-binary"), }, }, { logicalFixture: "redis-server/7.0.14/linux-amd64", expected: pkg.Package{ Name: "redis", Version: "7.0.14", Type: "binary", PURL: "pkg:generic/redis@7.0.14", Locations: locations("redis-server"), Metadata: metadata("redis-binary"), }, }, { // note: dynamic (non-snippet) test case logicalFixture: "redis-server/7.2.3/linux-amd64", expected: pkg.Package{ Name: "redis", Version: "7.2.3", Type: "binary", PURL: "pkg:generic/redis@7.2.3", Locations: locations("redis-server"), Metadata: metadata("redis-binary"), }, }, { // note: dynamic (non-snippet) test case logicalFixture: "redis-server/7.2.3/linux-arm64", expected: pkg.Package{ Name: "redis", Version: "7.2.3", Type: "binary", PURL: "pkg:generic/redis@7.2.3", Locations: locations("redis-server"), Metadata: metadata("redis-binary"), }, }, { logicalFixture: "redis-server/7.2.5/linux-386", expected: pkg.Package{ Name: "redis", Version: "7.2.5", Type: "binary", PURL: "pkg:generic/redis@7.2.5", Locations: locations("redis-server"), Metadata: metadata("redis-binary"), }, }, { logicalFixture: "valkey-server/9.0.0/linux-amd64", expected: pkg.Package{ Name: "valkey", Version: "9.0.0", Type: "binary", PURL: "pkg:generic/valkey@9.0.0", Locations: locations("valkey-server"), Metadata: metadata("valkey-binary"), }, }, { // no python binary, but we find libpython, which is surfaced as primary evidence logicalFixture: "python-shared-lib/3.7.4/linux-amd64", expected: pkg.Package{ Name: "python", Version: "3.7.4", PURL: "pkg:generic/python@3.7.4", Locations: locations("libpython3.7m.so.1.0"), Metadata: metadata("python-binary-lib"), }, }, { // note: dynamic (non-snippet) test case logicalFixture: "python-slim-shared-libs/3.11/linux-amd64", expected: pkg.Package{ Name: "python", Version: "3.11.2", PURL: "pkg:generic/python@3.11.2", Locations: locations("python3.11", "libpython3.11.so.1.0"), Metadata: pkg.BinarySignature{ Matches: []pkg.ClassifierMatch{ match("python-binary", "python3.11"), match("python-binary", "libpython3.11.so.1.0"), }, }, }, }, { // note: dynamic (non-snippet) test case logicalFixture: "python-rhel-shared-libs/3.9/linux-amd64", expected: pkg.Package{ Name: "python", Version: "3.9.13", PURL: "pkg:generic/python@3.9.13", Locations: locations("python3.9", "libpython3.9.so.1.0"), Metadata: pkg.BinarySignature{ Matches: []pkg.ClassifierMatch{ match("python-binary", "python3.9"), match("python-binary", "libpython3.9.so.1.0"), }, }, }, }, { // note: dynamic (non-snippet) test case logicalFixture: "python3.9/3.9.16/linux-amd64", expected: pkg.Package{ Name: "python", Version: "3.9.2", PURL: "pkg:generic/python@3.9.2", Locations: locations("python3.9"), Metadata: pkg.BinarySignature{ Matches: []pkg.ClassifierMatch{ match("python-binary", "python3.9"), }, }, }, }, { // note: dynamic (non-snippet) test case logicalFixture: "python-alpine-shared-libs/3.4/linux-amd64", expected: pkg.Package{ Name: "python", Version: "3.4.10", PURL: "pkg:generic/python@3.4.10", Locations: locations("python3.4", "libpython3.4m.so.1.0"), Metadata: pkg.BinarySignature{ Matches: []pkg.ClassifierMatch{ match("python-binary", "python3.4"), match("python-binary", "libpython3.4m.so.1.0"), }, }, }, }, { // TODO: find original binary... // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo logicalFixture: "python-with-incorrect-match/3.5.3/linux-amd64", expected: pkg.Package{ Name: "python", Version: "3.5.3", PURL: "pkg:generic/python@3.5.3", Locations: locations("python3.5"), Metadata: metadata("python-binary"), }, }, { // TODO: find original binary... // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo logicalFixture: "python/3.6.3/linux-amd64", expected: pkg.Package{ Name: "python", Version: "3.6.3", PURL: "pkg:generic/python@3.6.3", Locations: locations("python3.6"), Metadata: metadata("python-binary"), }, }, { // TODO: find original binary... // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo logicalFixture: "python-duplicates/3.8.16/linux-amd64", expected: pkg.Package{ Name: "python", Version: "3.8.16", Type: "binary", PURL: "pkg:generic/python@3.8.16", Locations: locations("dir/python3.8", "python3.8", "libpython3.8.so"), Metadata: pkg.BinarySignature{ Matches: []pkg.ClassifierMatch{ match("python-binary", "dir/python3.8"), match("python-binary", "python3.8"), match("python-binary-lib", "libpython3.8.so"), }, }, }, }, { logicalFixture: "pypy-shared-lib/7.3.14/linux-amd64", expected: pkg.Package{ Name: "pypy", Version: "7.3.14", PURL: "pkg:generic/pypy@7.3.14", Locations: locations("libpypy3.9-c.so"), Metadata: metadata("pypy-binary-lib"), }, }, { logicalFixture: "go/1.21.3/linux-amd64", expected: pkg.Package{ Name: "go", Version: "1.21.3", PURL: "pkg:generic/go@1.21.3", Locations: locations("go"), Metadata: metadata("go-binary"), }, }, { logicalFixture: "node/0.10.48/linux-amd64", expected: pkg.Package{ Name: "node", Version: "0.10.48", PURL: "pkg:generic/node@0.10.48", Locations: locations("node"), Metadata: metadata("nodejs-binary"), }, }, { logicalFixture: "node/0.12.18/linux-amd64", expected: pkg.Package{ Name: "node", Version: "0.12.18", PURL: "pkg:generic/node@0.12.18", Locations: locations("node"), Metadata: metadata("nodejs-binary"), }, }, { logicalFixture: "node/4.9.1/linux-amd64", expected: pkg.Package{ Name: "node", Version: "4.9.1", PURL: "pkg:generic/node@4.9.1", Locations: locations("node"), Metadata: metadata("nodejs-binary"), }, }, { logicalFixture: "node/19.2.0/linux-amd64", expected: pkg.Package{ Name: "node", Version: "19.2.0", PURL: "pkg:generic/node@19.2.0", Locations: locations("node"), Metadata: metadata("nodejs-binary"), }, }, { // TODO: find original binary... // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo logicalFixture: "go-version-hint/1.15-dev/any", expected: pkg.Package{ Name: "go", Version: "1.15", PURL: "pkg:generic/go@1.15", Locations: locations("bin/go", "VERSION"), Metadata: metadata("go-binary"), }, }, { logicalFixture: "go-version-hint/1.15/any", expected: pkg.Package{ Name: "go", Version: "1.15", PURL: "pkg:generic/go@1.15", Locations: locations("bin/go", "VERSION"), Metadata: metadata("go-binary"), }, }, { logicalFixture: "go-version-hint/1.15w/any", expected: pkg.Package{ Name: "go", Version: "1.15", PURL: "pkg:generic/go@1.15", Locations: locations("bin/go.exe", "VERSION"), Metadata: metadata("go-binary"), }, }, { logicalFixture: "go-version-hint/1.21/any", expected: pkg.Package{ Name: "go", Version: "1.21", PURL: "pkg:generic/go@1.21", Locations: locations("go", "VERSION"), Metadata: metadata("go-binary"), }, }, { // note: this is for compatability with dev version of golang tip image, which resolves the issue #3681 logicalFixture: "go-version-hint/1.25/any", expected: pkg.Package{ Name: "go", Version: "1.25-d524e1e", PURL: "pkg:generic/go@1.25-d524e1e", Locations: locations("bin/go", "VERSION.cache"), Metadata: metadata("go-binary"), }, }, { logicalFixture: "go-version-hint/1.25w/any", expected: pkg.Package{ Name: "go", Version: "1.25-d524e1e", PURL: "pkg:generic/go@1.25-d524e1e", Locations: locations("go.exe", "VERSION"), Metadata: metadata("go-binary"), }, }, { // note: this is testing BUSYBOX which is typically through a link to "[" (in this case a symlink but in // practice this is often a hard link). logicalFixture: `busybox/1.36.1/linux-amd64`, expected: pkg.Package{ Name: "busybox", Version: "1.36.1", PURL: "pkg:generic/busybox@1.36.1", Locations: locations("["), // note: busybox is a link to [ Metadata: metadata("busybox-binary", "[", "busybox"), }, }, { logicalFixture: `util-linux/2.37.4/linux-amd64`, expected: pkg.Package{ Name: "util-linux", Version: "2.37.4", PURL: "pkg:generic/util-linux@2.37.4", Locations: locations("getopt"), Metadata: metadata("util-linux-binary"), }, }, { logicalFixture: "java-jre-openjdk/1.8.0_352-b08/linux-amd64", expected: pkg.Package{ Name: "openjdk", Version: "1.8.0_352-b08", Type: "binary", PURL: "pkg:generic/oracle/openjdk@1.8.0_352-b08", Locations: locations("java"), Metadata: metadata("java-binary-openjdk-with-update", "java"), }, }, { logicalFixture: "java-jre-openjdk/11.0.17/linux-amd64", expected: pkg.Package{ Name: "openjdk", Version: "11.0.17+8-LTS", Type: "binary", PURL: "pkg:generic/oracle/openjdk@11.0.17%2B8-LTS", Locations: locations("java"), Metadata: metadata("java-binary-openjdk", "java"), }, }, { logicalFixture: "java-jre-openjdk-eclipse/11.0.22/linux-amd64", expected: pkg.Package{ Name: "openjdk", Version: "11.0.22+7", Type: "binary", PURL: "pkg:generic/oracle/openjdk@11.0.22%2B7", Locations: locations("java"), Metadata: metadata("java-binary-openjdk", "java"), }, }, { logicalFixture: "java-jre-openjdk-arm64-eclipse/11.0.22/linux-arm64", expected: pkg.Package{ Name: "openjdk", Version: "11.0.22+7", Type: "binary", PURL: "pkg:generic/oracle/openjdk@11.0.22%2B7", Locations: locations("java"), Metadata: metadata("java-binary-openjdk", "java"), }, }, { logicalFixture: "java-graal-openjdk/17.0.3+7-jvmci-22.1-b06/linux-amd64", expected: pkg.Package{ Name: "graalvm", Version: "17.0.3+7-jvmci-22.1-b06", Type: "binary", PURL: "pkg:generic/oracle/graalvm@17.0.3%2B7-jvmci-22.1-b06", Locations: locations("java"), Metadata: metadata("java-binary-graalvm", "java"), }, }, { // TODO: find original binary... // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo logicalFixture: "java-jre-oracle/19.0.1/linux-amd64", expected: pkg.Package{ Name: "jre", Version: "19.0.1+10-21", Type: "binary", PURL: "pkg:generic/oracle/jre@19.0.1%2B10-21", Locations: locations("java"), Metadata: metadata("java-binary-oracle", "java"), }, }, { // TODO: find original binary... // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo logicalFixture: "java-jre-oracle/19.0.1/darwin", expected: pkg.Package{ Name: "jre", Version: "19.0.1+10-21", Type: "binary", PURL: "pkg:generic/oracle/jre@19.0.1%2B10-21", Locations: locations("java"), Metadata: metadata("java-binary-oracle", "java"), }, }, { logicalFixture: "java-jdk-openjdk/21.0.2+13-LTS/linux-amd64", expected: pkg.Package{ Name: "openjdk", Version: "21.0.2+13-LTS", Type: "binary", PURL: "pkg:generic/oracle/openjdk@21.0.2%2B13-LTS", Locations: locations("jdb"), Metadata: metadata("java-binary-openjdk-fallthrough", "jdb"), }, }, { logicalFixture: "rust-libstd/1.50.0/linux-amd64", expected: pkg.Package{ Name: "rust", Version: "1.50.0", Type: "binary", PURL: "pkg:generic/rust@1.50.0", Locations: locations("libstd-6f77337c1826707d.so"), Metadata: metadata("rust-standard-library-linux"), }, }, { // TODO: find original binary... // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo logicalFixture: "rust-libstd/1.50.0/darwin", expected: pkg.Package{ Name: "rust", Version: "1.50.0", Type: "binary", PURL: "pkg:generic/rust@1.50.0", Locations: locations("libstd-f6f9eec1635e636a.dylib"), Metadata: metadata("rust-standard-library-macos"), }, }, { // TODO: find original binary... // note: cannot find the original binary, using a custom snippet based on the original snippet in the repo logicalFixture: "rust-libstd/1.67.1/darwin", expected: pkg.Package{ Name: "rust", Version: "1.67.1", Type: "binary", PURL: "pkg:generic/rust@1.67.1", Locations: locations("libstd-16f2b65e77054c42.dylib"), Metadata: metadata("rust-standard-library-macos"), }, }, { logicalFixture: "rust-libstd-musl/1.67.1/linux-amd64", expected: pkg.Package{ Name: "rust", Version: "1.67.1", Type: "binary", PURL: "pkg:generic/rust@1.67.1", Locations: locations("libstd-86aefecbddda356d.so"), Metadata: metadata("rust-standard-library-linux"), }, }, { logicalFixture: "rust-libstd/1.67.1/linux-amd64", expected: pkg.Package{ Name: "rust", Version: "1.67.1", Type: "binary", PURL: "pkg:generic/rust@1.67.1", Locations: locations("libstd-c6192dd4c4d410ac.so"), Metadata: metadata("rust-standard-library-linux"), }, }, { // note: dynamic (non-snippet) test case name: "positive-ruby-3.2.1", logicalFixture: "ruby-bullseye-shared-libs/3.2.1/linux-amd64", expected: pkg.Package{ Name: "ruby", Version: "3.2.1", Type: "binary", PURL: "pkg:generic/ruby@3.2.1", Locations: locations("ruby", "libruby.so.3.2.1"), Metadata: pkg.BinarySignature{ Matches: []pkg.ClassifierMatch{ match("ruby-binary", "ruby"), match("ruby-binary", "libruby.so.3.2.1"), }, }, }, }, { // note: dynamic (non-snippet) test case name: "positive-ruby-3.4.0-dev", logicalFixture: "ruby-shared-libs/3.4.0-dev/linux-amd64", expected: pkg.Package{ Name: "ruby", Version: "3.4.0dev", Type: "binary", PURL: "pkg:generic/ruby@3.4.0dev", Locations: locations("ruby", "libruby.so.3.4.0"), Metadata: pkg.BinarySignature{ Matches: []pkg.ClassifierMatch{ match("ruby-binary", "ruby"), match("ruby-binary", "libruby.so.3.4.0"), }, }, }, }, { // note: dynamic (non-snippet) test case name: "positive-ruby-3.4.0-preview1", logicalFixture: "ruby-shared-libs/3.4.0-preview1/linux-amd64", expected: pkg.Package{ Name: "ruby", Version: "3.4.0preview1", Type: "binary", PURL: "pkg:generic/ruby@3.4.0preview1", Locations: locations("ruby", "libruby.so.3.4.0"), Metadata: pkg.BinarySignature{ Matches: []pkg.ClassifierMatch{ match("ruby-binary", "ruby"), match("ruby-binary", "libruby.so.3.4.0"), }, }, }, }, { // note: dynamic (non-snippet) test case name: "positive-ruby-3.3.0-rc1", logicalFixture: "ruby-shared-libs/3.3.0-rc1/linux-amd64", expected: pkg.Package{ Name: "ruby", Version: "3.3.0rc1", Type: "binary", PURL: "pkg:generic/ruby@3.3.0rc1", Locations: locations("ruby", "libruby.so.3.3.0"), Metadata: pkg.BinarySignature{ Matches: []pkg.ClassifierMatch{ match("ruby-binary", "ruby"), match("ruby-binary", "libruby.so.3.3.0"), }, }, }, }, { // note: dynamic (non-snippet) test case logicalFixture: "ruby-bullseye-shared-libs/2.7.7/linux-amd64", expected: pkg.Package{ Name: "ruby", Version: "2.7.7p221", Type: "binary", PURL: "pkg:generic/ruby@2.7.7p221", Locations: locations("ruby", "libruby.so.2.7.7"), Metadata: pkg.BinarySignature{ Matches: []pkg.ClassifierMatch{ match("ruby-binary", "ruby"), match("ruby-binary", "libruby.so.2.7.7"), }, }, }, }, { // note: dynamic (non-snippet) test case logicalFixture: "ruby-shared-libs/2.6.10/linux-amd64", expected: pkg.Package{ Name: "ruby", Version: "2.6.10p210", Type: "binary", PURL: "pkg:generic/ruby@2.6.10p210", Locations: locations("ruby", "libruby.so.2.6.10"), Metadata: pkg.BinarySignature{ Matches: []pkg.ClassifierMatch{ match("ruby-binary", "ruby"), match("ruby-binary", "libruby.so.2.6.10"), }, }, }, }, { logicalFixture: "ruby/1.9.3p551/linux-amd64", expected: pkg.Package{ Name: "ruby", Version: "1.9.3p551", Type: "binary", PURL: "pkg:generic/ruby@1.9.3p551", Locations: locations("ruby"), Metadata: metadata("ruby-binary"), }, }, { logicalFixture: "consul/1.15.2/linux-amd64", expected: pkg.Package{ Name: "consul", Version: "1.15.2", Type: "binary", PURL: "pkg:golang/github.com/hashicorp/consul@1.15.2", Locations: locations("consul"), Metadata: metadata("consul-binary"), }, }, { logicalFixture: "vault/1.20.2/linux-amd64", expected: pkg.Package{ Name: "github.com/hashicorp/vault", Version: "1.20.2", Type: "golang", PURL: "pkg:golang/github.com/hashicorp/vault@1.20.2", Locations: locations("vault"), Metadata: metadata("hashicorp-vault-binary"), }, }, { logicalFixture: "vault/1.19.4/linux-arm64", expected: pkg.Package{ Name: "github.com/hashicorp/vault", Version: "1.19.4", Type: "golang", PURL: "pkg:golang/github.com/hashicorp/vault@1.19.4", Locations: locations("vault"), Metadata: metadata("hashicorp-vault-binary"), }, }, { logicalFixture: "erlang/25.3.2.6/linux-amd64", expected: pkg.Package{ Name: "erlang", Version: "25.3.2.6", Type: "binary", PURL: "pkg:generic/erlang@25.3.2.6", Locations: locations("erlexec"), Metadata: metadata("erlang-binary"), }, }, { logicalFixture: "erlang/26.2.0.0/linux-amd64", expected: pkg.Package{ Name: "erlang", Version: "26.2", Type: "binary", PURL: "pkg:generic/erlang@26.2", Locations: locations("erlexec"), Metadata: metadata("erlang-binary"), }, }, { logicalFixture: "erlang/26.2.4/linux-amd64", expected: pkg.Package{ Name: "erlang", Version: "26.2.4", Type: "binary", PURL: "pkg:generic/erlang@26.2.4", Locations: locations("liberts_internal.a"), Metadata: metadata("erlang-library"), }, }, { logicalFixture: "erlang/27.0/linux-amd64", expected: pkg.Package{ Name: "erlang", Version: "27.0", Type: "binary", PURL: "pkg:generic/erlang@27.0", Locations: locations("beam.smp"), Metadata: metadata("erlang-alpine-binary"), }, }, { logicalFixture: "erlang/26.1.2/linux-arm64", expected: pkg.Package{ Name: "erlang", Version: "26.1.2", Type: "binary", PURL: "pkg:generic/erlang@26.1.2", Locations: locations("beam.smp"), Metadata: metadata("erlang-alpine-binary"), }, }, { logicalFixture: "swipl/9.3.8/linux-amd64", expected: pkg.Package{ Name: "swipl", Version: "9.3.8", Type: "binary", PURL: "pkg:generic/swipl@9.3.8", Locations: locations("swipl"), Metadata: metadata("swipl-binary"), }, }, { logicalFixture: "dart/2.12.4/linux-amd64", expected: pkg.Package{ Name: "dart", Version: "2.12.4", Type: "binary", PURL: "pkg:generic/dart@2.12.4", Locations: locations("dart"), Metadata: metadata("dart-binary"), }, }, { logicalFixture: "dart/3.0.0/linux-arm", expected: pkg.Package{ Name: "dart", Version: "3.0.0", Type: "binary", PURL: "pkg:generic/dart@3.0.0", Locations: locations("dart"), Metadata: metadata("dart-binary"), }, }, { logicalFixture: "dart/3.5.2/linux-amd64", expected: pkg.Package{ Name: "dart", Version: "3.5.2", Type: "binary", PURL: "pkg:generic/dart@3.5.2", Locations: locations("dart"), Metadata: metadata("dart-binary"), }, }, { logicalFixture: "dart/3.6.0-216.1.beta/linux-amd64", expected: pkg.Package{ Name: "dart", Version: "3.6.0-216.1.beta", Type: "binary", PURL: "pkg:generic/dart@3.6.0-216.1.beta", Locations: locations("dart"), Metadata: metadata("dart-binary"), }, }, { logicalFixture: "haskell-ghc/9.6.5/linux-amd64", expected: pkg.Package{ Name: "haskell/ghc", Version: "9.6.5", Type: "binary", PURL: "pkg:generic/haskell/ghc@9.6.5", Locations: locations("ghc-9.6.5"), Metadata: metadata("haskell-ghc-binary"), }, }, { logicalFixture: "haskell-cabal/3.10.3.0/linux-amd64", expected: pkg.Package{ Name: "haskell/cabal", Version: "3.10.3.0", Type: "binary", PURL: "pkg:generic/haskell/cabal@3.10.3.0", Locations: locations("cabal"), Metadata: metadata("haskell-cabal-binary"), }, }, { logicalFixture: "nginx/1.25.1/linux-amd64", expected: pkg.Package{ Name: "nginx", Version: "1.25.1", Type: "binary", PURL: "pkg:generic/nginx@1.25.1", Locations: locations("nginx"), Metadata: metadata("nginx-binary"), }, }, { logicalFixture: "nginx-openresty/1.21.4.3/linux-amd64", expected: pkg.Package{ Name: "nginx", Version: "1.21.4", Type: "binary", PURL: "pkg:generic/nginx@1.21.4", Locations: locations("nginx"), Metadata: metadata("nginx-binary"), }, }, { logicalFixture: "bash/5.1.16/linux-amd64", expected: pkg.Package{ Name: "bash", Version: "5.1.16", Type: "binary", PURL: "pkg:generic/bash@5.1.16", Locations: locations("bash"), Metadata: metadata("bash-binary"), }, }, { logicalFixture: "openssl/3.1.4/linux-amd64", expected: pkg.Package{ Name: "openssl", Version: "3.1.4", Type: "binary", PURL: "pkg:generic/openssl@3.1.4", Locations: locations("openssl"), Metadata: metadata("openssl-binary"), }, }, { logicalFixture: "openssl/1.1.1w/linux-arm64", expected: pkg.Package{ Name: "openssl", Version: "1.1.1w", Type: "binary", PURL: "pkg:generic/openssl@1.1.1w", Locations: locations("openssl"), Metadata: metadata("openssl-binary"), }, }, { logicalFixture: "openssl/1.1.1zb/linux-arm64", expected: pkg.Package{ Name: "openssl", Version: "1.1.1zb", Type: "binary", PURL: "pkg:generic/openssl@1.1.1zb", Locations: locations("openssl"), Metadata: metadata("openssl-binary"), }, }, { logicalFixture: "qt/4.8.6/linux-amd64", expected: pkg.Package{ Name: "qtbase", Version: "4.8.6", Type: "binary", PURL: "pkg:generic/qtbase@4.8.6", Locations: locations("libQtCore.so.4.8.6"), Metadata: metadata("qt-qtbase-lib"), }, }, { logicalFixture: "qt/5.15.2/linux-amd64", expected: pkg.Package{ Name: "qtbase", Version: "5.15.2", Type: "binary", PURL: "pkg:generic/qtbase@5.15.2", Locations: locations("libQt5Core.so.5.15.2"), Metadata: metadata("qt-qtbase-lib"), }, }, { logicalFixture: "qt/6.5.0/linux-amd64", expected: pkg.Package{ Name: "qtbase", Version: "6.5.0", Type: "binary", PURL: "pkg:generic/qtbase@6.5.0", Locations: locations("libQt6Core.so.6.5.0"), Metadata: metadata("qt-qtbase-lib"), }, }, { logicalFixture: "gcc/12.3.0/linux-amd64", expected: pkg.Package{ Name: "gcc", Version: "12.3.0", Type: "binary", PURL: "pkg:generic/gcc@12.3.0", Locations: locations("gcc"), Metadata: metadata("gcc-binary"), }, }, { logicalFixture: "fluent-bit/3.0.2/linux-amd64", expected: pkg.Package{ Name: "fluent-bit", Version: "3.0.2", Type: "binary", PURL: "pkg:github/fluent/fluent-bit@3.0.2", Locations: locations("fluent-bit"), Metadata: metadata("fluent-bit-binary"), }, }, { logicalFixture: "fluent-bit/2.2.1/linux-arm64", expected: pkg.Package{ Name: "fluent-bit", Version: "2.2.1", Type: "binary", PURL: "pkg:github/fluent/fluent-bit@2.2.1", Locations: locations("fluent-bit"), Metadata: metadata("fluent-bit-binary"), }, }, { logicalFixture: "fluent-bit/1.7.0-dev-3/linux-amd64", expected: pkg.Package{ Name: "fluent-bit", Version: "1.7.0", Type: "binary", PURL: "pkg:github/fluent/fluent-bit@1.7.0", Locations: locations("fluent-bit"), Metadata: metadata("fluent-bit-binary"), }, }, { logicalFixture: "fluent-bit/1.3.10/linux-arm", expected: pkg.Package{ Name: "fluent-bit", Version: "1.3.10", Type: "binary", PURL: "pkg:github/fluent/fluent-bit@1.3.10", Locations: locations("fluent-bit"), Metadata: metadata("fluent-bit-binary"), }, }, { logicalFixture: "wp/2.9.0/linux-amd64", expected: pkg.Package{ Name: "wp-cli", Version: "2.9.0", Type: "binary", PURL: "pkg:generic/wp-cli@2.9.0", Locations: locations("wp"), Metadata: metadata("wordpress-cli-binary"), }, }, { logicalFixture: "lighttpd/1.4.76/linux-amd64", expected: pkg.Package{ Name: "lighttpd", Version: "1.4.76", Type: "binary", PURL: "pkg:generic/lighttpd@1.4.76", Locations: locations("lighttpd"), Metadata: metadata("lighttpd-binary"), }, }, { logicalFixture: "proftpd/1.3.8b/linux-amd64", expected: pkg.Package{ Name: "proftpd", Version: "1.3.8b", Type: "binary", PURL: "pkg:generic/proftpd@1.3.8b", Locations: locations("proftpd"), Metadata: metadata("proftpd-binary"), }, }, { logicalFixture: "zstd/1.5.6/linux-amd64", expected: pkg.Package{ Name: "zstd", Version: "1.5.6", Type: "binary", PURL: "pkg:generic/zstd@1.5.6", Locations: locations("zstd"), Metadata: metadata("zstd-binary"), }, }, { logicalFixture: "zstd/1.5.6/linux-amd64", expected: pkg.Package{ Name: "zstd", Version: "1.5.6", Type: "binary", PURL: "pkg:generic/zstd@1.5.6", Locations: locations("zstd"), Metadata: metadata("zstd-binary"), }, }, { logicalFixture: "xz/5.6.2/linux-amd64", expected: pkg.Package{ Name: "xz", Version: "5.6.2", Type: "binary", PURL: "pkg:generic/xz@5.6.2", Locations: locations("xz"), Metadata: metadata("xz-binary"), }, }, { logicalFixture: "gzip/1.12/linux-amd64", expected: pkg.Package{ Name: "gzip", Version: "1.12", Type: "binary", PURL: "pkg:generic/gzip@1.12", Locations: locations("gzip"), Metadata: metadata("gzip-binary"), }, }, { logicalFixture: "sqlcipher/4.5.5/linux-amd64", expected: pkg.Package{ Name: "sqlcipher", Version: "4.5.5", Type: "binary", PURL: "pkg:generic/sqlcipher@4.5.5", Locations: locations("sqlcipher"), Metadata: metadata("sqlcipher-binary"), }, }, { logicalFixture: "jq/1.7.1/linux-amd64", expected: pkg.Package{ Name: "jq", Version: "1.7.1", Type: "binary", PURL: "pkg:generic/jq@1.7.1", Locations: locations("jq"), Metadata: metadata("jq-binary"), }, }, { logicalFixture: "chrome/126.0.6478.182/linux-amd64", expected: pkg.Package{ Name: "chrome", Version: "126.0.6478.182", Type: "binary", PURL: "pkg:generic/chrome@126.0.6478.182", Locations: locations("chrome"), Metadata: metadata("chrome-binary"), }, }, { logicalFixture: "chrome/127.0.6533.119/linux-amd64", expected: pkg.Package{ Name: "chrome", Version: "127.0.6533.119", Type: "binary", PURL: "pkg:generic/chrome@127.0.6533.119", Locations: locations("chrome"), Metadata: metadata("chrome-binary"), }, }, { logicalFixture: "ffmpeg/7.1.1/darwin-arm64", expected: pkg.Package{ Name: "ffmpeg", Version: "7.1.1", Type: "binary", PURL: "pkg:generic/ffmpeg@7.1.1", Locations: locations("ffmpeg"), Metadata: metadata("ffmpeg-binary"), }, }, { logicalFixture: "ffmpeg/6.1.1/linux-amd64", expected: pkg.Package{ Name: "ffmpeg", Version: "6.1.1", Type: "binary", PURL: "pkg:generic/ffmpeg@6.1.1", Locations: locations("ffmpeg"), Metadata: metadata("ffmpeg-binary"), }, }, { logicalFixture: "ffmpeg-shared-libs/5.1.4/linux-amd64", expected: pkg.Package{ Name: "ffmpeg", Version: "5.1.4", Type: "binary", PURL: "pkg:generic/ffmpeg@5.1.4", Locations: locations("libavcodec-9aae324f.so.59.37.100"), Metadata: metadata("ffmpeg-library"), }, }, { logicalFixture: "elixir/1.19.1/linux-amd64", expected: pkg.Package{ Name: "elixir", Version: "1.19.1", Type: "binary", PURL: "pkg:generic/elixir@1.19.1", Locations: locations("elixir", "lib/elixir/ebin/elixir.app"), Metadata: pkg.BinarySignature{ Matches: []pkg.ClassifierMatch{ match("elixir-binary", "elixir"), match("elixir-library", "lib/elixir/ebin/elixir.app"), }, }, }, }, { logicalFixture: "istio_pilot-discovery/1.26.8/linux-amd64", expected: pkg.Package{ Name: "pilot-discovery", Version: "1.26.8", Type: "binary", PURL: "pkg:generic/istio@1.26.8", Locations: locations("pilot-discovery"), Metadata: metadata("istio-binary"), }, }, { logicalFixture: "istio_pilot-discovery/1.8.0/linux-amd64", expected: pkg.Package{ Name: "pilot-discovery", Version: "1.8.0", Type: "binary", PURL: "pkg:generic/istio@1.8.0", Locations: locations("pilot-discovery"), Metadata: metadata("istio-binary"), }, }, { logicalFixture: "istio_pilot-discovery/1.3.8/linux-amd64", expected: pkg.Package{ Name: "pilot-discovery", Version: "1.3.8", Type: "binary", PURL: "pkg:generic/istio@1.3.8", Locations: locations("pilot-discovery"), Metadata: metadata("istio-binary"), }, }, { logicalFixture: "istio_pilot-discovery/1.1.17/linux-amd64", expected: pkg.Package{ Name: "pilot-discovery", Version: "1.1.17", Type: "binary", PURL: "pkg:generic/istio@1.1.17", Locations: locations("pilot-discovery"), Metadata: metadata("istio-binary"), }, }, { logicalFixture: "istio_pilot-agent/1.26.8/linux-amd64", expected: pkg.Package{ Name: "pilot-agent", Version: "1.26.8", Type: "binary", PURL: "pkg:generic/istio@1.26.8", Locations: locations("pilot-agent"), Metadata: metadata("istio-binary"), }, }, { logicalFixture: "istio_pilot-agent/1.8.0/linux-amd64", expected: pkg.Package{ Name: "pilot-agent", Version: "1.8.0", Type: "binary", PURL: "pkg:generic/istio@1.8.0", Locations: locations("pilot-agent"), Metadata: metadata("istio-binary"), }, }, { logicalFixture: "istio_pilot-agent/1.1.17/linux-amd64", expected: pkg.Package{ Name: "pilot-agent", Version: "1.1.17", Type: "binary", PURL: "pkg:generic/istio@1.1.17", Locations: locations("pilot-agent"), Metadata: metadata("istio-binary"), }, }, { logicalFixture: "grafana/12.4.0-22081664032/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "12.4.0-22081664032", Type: "binary", PURL: "pkg:generic/grafana@12.4.0-22081664032", Locations: locations("grafana"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "grafana/12.3.2-security-01/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "12.3.2", Type: "binary", PURL: "pkg:generic/grafana@12.3.2", Locations: locations("grafana"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "grafana/12.3.1/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "12.3.1", Type: "binary", PURL: "pkg:generic/grafana@12.3.1", Locations: locations("grafana"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "grafana/12.2.0-258092/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "12.2.0-258092", Type: "binary", PURL: "pkg:generic/grafana@12.2.0-258092", Locations: locations("grafana"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "grafana/12.0.0/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "12.0.0", Type: "binary", PURL: "pkg:generic/grafana@12.0.0", Locations: locations("grafana"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "grafana/11.0.0-preview/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "11.0.0-preview", Type: "binary", PURL: "pkg:generic/grafana@11.0.0-preview", Locations: locations("grafana"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "grafana/11.0.0/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "11.0.0", Type: "binary", PURL: "pkg:generic/grafana@11.0.0", Locations: locations("grafana"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "grafana/10.4.19/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "10.4.19", Type: "binary", PURL: "pkg:generic/grafana@10.4.19", Locations: locations("grafana"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "grafana/10.3.12/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "10.3.12", Type: "binary", PURL: "pkg:generic/grafana@10.3.12", Locations: locations("grafana"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "grafana/9.5.21/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "9.5.21", Type: "binary", PURL: "pkg:generic/grafana@9.5.21", Locations: locations("grafana"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "grafana/9.4.0-beta1/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "9.4.0-beta1", Type: "binary", PURL: "pkg:generic/grafana@9.4.0-beta1", Locations: locations("grafana"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "grafana/9.3.0-beta1/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "9.3.0-beta1", Type: "binary", PURL: "pkg:generic/grafana@9.3.0-beta1", Locations: locations("grafana-server"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "grafana/9.2.20/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "9.2.20", Type: "binary", PURL: "pkg:generic/grafana@9.2.20", Locations: locations("grafana"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "grafana/9.2.13/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "9.2.13", Type: "binary", PURL: "pkg:generic/grafana@9.2.13", Locations: locations("grafana"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "grafana/9.0.0/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "9.0.0", Type: "binary", PURL: "pkg:generic/grafana@9.0.0", Locations: locations("grafana-server"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "grafana/7.5.17/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "7.5.17", Type: "binary", PURL: "pkg:generic/grafana@7.5.17", Locations: locations("grafana-server"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "grafana/6.7.6/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "6.7.6", Type: "binary", PURL: "pkg:generic/grafana@6.7.6", Locations: locations("grafana-server"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "grafana/6.7.0-test/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "6.7.0-test", Type: "binary", PURL: "pkg:generic/grafana@6.7.0-test", Locations: locations("grafana-server"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "grafana/6.0.0-beta1/linux-amd64", expected: pkg.Package{ Name: "grafana", Version: "6.0.0-beta1", Type: "binary", PURL: "pkg:generic/grafana@6.0.0-beta1", Locations: locations("grafana-server"), Metadata: metadata("grafana-binary"), }, }, { logicalFixture: "envoy/1.36.4/linux-amd64", expected: pkg.Package{ Name: "envoy", Version: "1.36.4", Type: "binary", PURL: "pkg:generic/envoy@1.36.4", Locations: locations("envoy"), Metadata: metadata("envoy-binary"), }, }, { logicalFixture: "envoy/1.34.5/linux-amd64", expected: pkg.Package{ Name: "envoy", Version: "1.34.5", Type: "binary", PURL: "pkg:generic/envoy@1.34.5", Locations: locations("envoy"), Metadata: metadata("envoy-binary"), }, }, { logicalFixture: "envoy/1.28.7/linux-amd64", expected: pkg.Package{ Name: "envoy", Version: "1.28.7", Type: "binary", PURL: "pkg:generic/envoy@1.28.7", Locations: locations("envoy"), Metadata: metadata("envoy-binary"), }, }, { logicalFixture: "envoy/1.22.11/linux-amd64", expected: pkg.Package{ Name: "envoy", Version: "1.22.11", Type: "binary", PURL: "pkg:generic/envoy@1.22.11", Locations: locations("envoy"), Metadata: metadata("envoy-binary"), }, }, { logicalFixture: "envoy/1.20.7/linux-amd64", expected: pkg.Package{ Name: "envoy", Version: "1.20.7", Type: "binary", PURL: "pkg:generic/envoy@1.20.7", Locations: locations("envoy"), Metadata: metadata("envoy-binary"), }, }, { logicalFixture: "envoy/1.18.6/linux-amd64", expected: pkg.Package{ Name: "envoy", Version: "1.18.6-dev", Type: "binary", PURL: "pkg:generic/envoy@1.18.6-dev", Locations: locations("envoy"), Metadata: metadata("envoy-binary"), }, }, { logicalFixture: "envoy/1.14.3/linux-amd64", expected: pkg.Package{ Name: "envoy", Version: "1.14.3", Type: "binary", PURL: "pkg:generic/envoy@1.14.3", Locations: locations("envoy"), Metadata: metadata("envoy-binary"), }, }, { logicalFixture: "envoy/1.11.0/linux-amd64", expected: pkg.Package{ Name: "envoy", Version: "1.11.0", Type: "binary", PURL: "pkg:generic/envoy@1.11.0", Locations: locations("envoy"), Metadata: metadata("envoy-binary"), }, }, { logicalFixture: "envoy/1.6.0/linux-amd64", expected: pkg.Package{ Name: "envoy", Version: "1.6.0", Type: "binary", PURL: "pkg:generic/envoy@1.6.0", Locations: locations("envoy"), Metadata: metadata("envoy-binary"), }, }, } for _, test := range tests { t.Run(test.logicalFixture, func(t *testing.T) { c := NewClassifierCataloger(DefaultClassifierCatalogerConfig()) // logicalFixture is the logical path to the full binary or snippet. This is relative to the testdata/classifiers/snippets // or testdata/classifiers/bin directory . Snippets are searched for first, and if not found, then existing binaries are // used. If no binary or snippet is found the test will fail. If '-must-use-original-binaries' is used the only // full binaries are tested (no snippets), and if no binary is found the test will be skipped. path := testutil.SnippetOrBinary(t, test.logicalFixture, *mustUseOriginalBinaries) src, err := directorysource.NewFromPath(path) require.NoError(t, err) resolver, err := src.FileResolver(source.SquashedScope) require.NoError(t, err) packages, _, err := c.Catalog(context.Background(), resolver) require.NoError(t, err) require.Len(t, packages, 1, "mismatched package count") assertPackagesAreEqual(t, test.expected, packages[0]) }) } } func Test_Cataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T) { tests := []struct { name string fixtureImage string expected pkg.Package }{ { name: "busybox-regression", fixtureImage: "image-busybox", expected: pkg.Package{ Name: "busybox", Version: "1.35.0", PURL: "pkg:generic/busybox@1.35.0", Locations: locations("/bin/["), Metadata: metadata("busybox-binary", "/bin/[", "/bin/busybox"), }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { c := NewClassifierCataloger(DefaultClassifierCatalogerConfig()) img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureImage) src := stereoscopesource.New(img, stereoscopesource.ImageConfig{ Reference: test.fixtureImage, }) resolver, err := src.FileResolver(source.SquashedScope) require.NoError(t, err) packages, _, err := c.Catalog(context.Background(), resolver) require.NoError(t, err) for _, p := range packages { assertPackagesAreEqual(t, test.expected, p) } }) } } func TestClassifierCataloger_DefaultClassifiers_NegativeCases(t *testing.T) { c := NewClassifierCataloger(DefaultClassifierCatalogerConfig()) src, err := directorysource.NewFromPath("testdata/classifiers/negative") assert.NoError(t, err) resolver, err := src.FileResolver(source.SquashedScope) assert.NoError(t, err) actualResults, _, err := c.Catalog(context.Background(), resolver) assert.NoError(t, err) assert.Equal(t, 0, len(actualResults)) } func Test_Cataloger_CustomClassifiers(t *testing.T) { defaultClassifers := DefaultClassifiers() golangExpected := pkg.Package{ Name: "go", Version: "1.14", PURL: "pkg:generic/go@1.14", Locations: locations("go"), Metadata: metadata("go-binary"), } customExpected := pkg.Package{ Name: "foo", Version: "1.2.3", PURL: "pkg:generic/foo@1.2.3", Locations: locations("foo"), Metadata: metadata("foo-binary"), } fooClassifier := binutils.Classifier{ Class: "foo-binary", FileGlob: "**/foo", EvidenceMatcher: binutils.FileContentsVersionMatcher( catalogerName, `(?m)foobar\s(?P[0-9]+\.[0-9]+\.[0-9]+)`, ), Package: "foo", PURL: mustPURL("pkg:generic/foo@version"), CPEs: singleCPE("cpe:2.3:a:foo:foo:*:*:*:*:*:*:*:*"), } tests := []struct { name string config ClassifierCatalogerConfig fixtureDir string expected *pkg.Package }{ { name: "empty-negative", config: ClassifierCatalogerConfig{ Classifiers: []binutils.Classifier{}, }, fixtureDir: "testdata/custom/go-1.14", expected: nil, }, { name: "default-positive", config: ClassifierCatalogerConfig{ Classifiers: defaultClassifers, }, fixtureDir: "testdata/custom/go-1.14", expected: &golangExpected, }, { name: "nodefault-negative", config: ClassifierCatalogerConfig{ Classifiers: []binutils.Classifier{fooClassifier}, }, fixtureDir: "testdata/custom/go-1.14", expected: nil, }, { name: "default-extended-positive", config: ClassifierCatalogerConfig{ Classifiers: append( append([]binutils.Classifier{}, defaultClassifers...), fooClassifier, ), }, fixtureDir: "testdata/custom/go-1.14", expected: &golangExpected, }, { name: "default-custom-negative", config: ClassifierCatalogerConfig{ Classifiers: append( append([]binutils.Classifier{}, defaultClassifers...), binutils.Classifier{ Class: "foo-binary", FileGlob: "**/foo", EvidenceMatcher: binutils.FileContentsVersionMatcher(catalogerName, `(?m)not there`), Package: "foo", PURL: mustPURL("pkg:generic/foo@version"), CPEs: singleCPE("cpe:2.3:a:foo:foo:*:*:*:*:*:*:*:*"), }, ), }, fixtureDir: "testdata/custom/extra", expected: nil, }, { name: "default-cutsom-positive", config: ClassifierCatalogerConfig{ Classifiers: append( append([]binutils.Classifier{}, defaultClassifers...), fooClassifier, ), }, fixtureDir: "testdata/custom/extra", expected: &customExpected, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { c := NewClassifierCataloger(test.config) src, err := directorysource.NewFromPath(test.fixtureDir) require.NoError(t, err) resolver, err := src.FileResolver(source.SquashedScope) require.NoError(t, err) packages, _, err := c.Catalog(context.Background(), resolver) require.NoError(t, err) if test.expected == nil { assert.Equal(t, 0, len(packages)) } else { require.Len(t, packages, 1) assertPackagesAreEqual(t, *test.expected, packages[0]) } }) } } func locations(locations ...string) file.LocationSet { var locs []file.Location for _, s := range locations { locs = append(locs, file.NewLocation(s)) } return file.NewLocationSet(locs...) } // metadata paths are: realPath, virtualPath func metadata(classifier string, paths ...string) pkg.BinarySignature { return pkg.BinarySignature{ Matches: []pkg.ClassifierMatch{ match(classifier, paths...), }, } } // match paths are: realPath, virtualPath func match(classifier string, paths ...string) pkg.ClassifierMatch { realPath := "" if len(paths) > 0 { realPath = paths[0] } virtualPath := "" if len(paths) > 1 { virtualPath = paths[1] } return pkg.ClassifierMatch{ Classifier: classifier, Location: file.NewVirtualLocationFromCoordinates( file.Coordinates{ RealPath: realPath, }, virtualPath, ), } } func assertPackagesAreEqual(t *testing.T, expected pkg.Package, p pkg.Package) { var failMessages []string expectedLocations := expected.Locations.ToSlice() gotLocations := p.Locations.ToSlice() if len(expectedLocations) != len(gotLocations) { failMessages = append(failMessages, fmt.Sprintf("locations are not equal: %v != %v", expectedLocations, gotLocations)) } else { for _, expectedLocation := range expectedLocations { if !slices.ContainsFunc(gotLocations, func(gotLocation file.Location) bool { return gotLocation.RealPath == expectedLocation.RealPath }) { failMessages = append(failMessages, fmt.Sprintf("location not found; expected: %v in set: %v", expectedLocation.RealPath, gotLocations)) } } } m1 := expected.Metadata.(pkg.BinarySignature).Matches m2 := p.Metadata.(pkg.BinarySignature).Matches matches := true if len(m1) == len(m2) { for i, m1 := range m1 { m2 := m2[i] if m1.Classifier != m2.Classifier { matches = false break } } } else { matches = false } if !matches { failMessages = append(failMessages, "classifier matches not equal") } if expected.Name != p.Name || expected.Version != p.Version || expected.PURL != p.PURL { failMessages = append(failMessages, "packages do not match") } if len(failMessages) > 0 { assert.Failf(t, strings.Join(failMessages, "; "), "diff: %s", cmp.Diff(expected, p, cmp.Transformer("Locations", func(l file.LocationSet) []file.Location { return l.ToSlice() }), cmpopts.IgnoreUnexported(pkg.Package{}, file.LocationData{}), cmpopts.IgnoreFields(pkg.Package{}, "CPEs", "FoundBy", "Type", "Locations", "Licenses"), ), ) } } type panicyResolver struct { searchCalled bool } func (p *panicyResolver) FilesByExtension(_ ...string) ([]file.Location, error) { p.searchCalled = true return nil, errors.New("not implemented") } func (p *panicyResolver) FilesByBasename(_ ...string) ([]file.Location, error) { p.searchCalled = true return nil, errors.New("not implemented") } func (p *panicyResolver) FilesByBasenameGlob(_ ...string) ([]file.Location, error) { p.searchCalled = true return nil, errors.New("not implemented") } func (p *panicyResolver) FileContentsByLocation(_ file.Location) (io.ReadCloser, error) { p.searchCalled = true return nil, errors.New("not implemented") } func (p *panicyResolver) HasPath(_ string) bool { return true } func (p *panicyResolver) FilesByPath(_ ...string) ([]file.Location, error) { p.searchCalled = true return nil, errors.New("not implemented") } func (p *panicyResolver) FilesByGlob(_ ...string) ([]file.Location, error) { p.searchCalled = true return nil, errors.New("not implemented") } func (p *panicyResolver) FilesByMIMEType(_ ...string) ([]file.Location, error) { p.searchCalled = true return nil, errors.New("not implemented") } func (p *panicyResolver) FilesByMediaType(_ ...string) ([]file.Location, error) { p.searchCalled = true return nil, errors.New("not implemented") } func (p *panicyResolver) RelativeFileByPath(_ file.Location, _ string) *file.Location { return nil } func (p *panicyResolver) AllLocations(_ context.Context) <-chan file.Location { return nil } func (p *panicyResolver) FileMetadataByLocation(_ file.Location) (file.Metadata, error) { return file.Metadata{}, errors.New("not implemented") } var _ file.Resolver = (*panicyResolver)(nil) func Test_Cataloger_ResilientToErrors(t *testing.T) { c := NewClassifierCataloger(DefaultClassifierCatalogerConfig()) resolver := &panicyResolver{} _, _, err := c.Catalog(context.Background(), resolver) assert.Nil(t, err) // non-coordinate-based FindBy* errors are now logged and not returned assert.True(t, resolver.searchCalled) } func TestCatalogerConfig_MarshalJSON(t *testing.T) { tests := []struct { name string cfg ClassifierCatalogerConfig want string wantErr assert.ErrorAssertionFunc }{ { name: "only show names of classes", cfg: ClassifierCatalogerConfig{ Classifiers: []binutils.Classifier{ { Class: "class", FileGlob: "glob", EvidenceMatcher: binutils.FileContentsVersionMatcher(catalogerName, ".thing"), Package: "pkg", PURL: packageurl.PackageURL{ Type: "type", Namespace: "namespace", Name: "name", Version: "version", Qualifiers: nil, Subpath: "subpath", }, CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*", cpe.GeneratedSource)}, }, }, }, want: `["class"]`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.wantErr == nil { tt.wantErr = assert.NoError } got, err := tt.cfg.MarshalJSON() if !tt.wantErr(t, err) { return } assert.Equal(t, tt.want, string(got)) }) } }