diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index bf32d182829..ec7d14e2a85 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -4,6 +4,8 @@ import ( "errors" "fmt" "io" + "os" + "path/filepath" "strings" "testing" @@ -18,15 +20,199 @@ import ( "github.com/anchore/syft/syft/source" ) -func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { +func Test_Cataloger_DefaultClassifiers_DynamicCases(t *testing.T) { tests := []struct { name string fixtureDir string expected pkg.Package }{ { - name: "positive-postgresql-15beta4", - fixtureDir: "test-fixtures/classifiers/positive/postgresql-15beta4", + name: "positive-helm-3.11.1", + fixtureDir: "test-fixtures/classifiers/dynamic/helm-3.11.1", + 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"), + }, + }, + { + name: "positive-helm-3.10.3", + fixtureDir: "test-fixtures/classifiers/dynamic/helm-3.10.3", + 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"), + }, + }, + { + name: "positive-python-3.11.2-from-shared-lib", + fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-shared-lib-3.11", + expected: pkg.Package{ + Name: "python", + Version: "3.11.2", + PURL: "pkg:generic/python@3.11.2", + Locations: locations("python3", "libpython3.11.so.1.0"), + Metadata: pkg.BinarySignature{ + Matches: []pkg.ClassifierMatch{ + match("python-binary", "python3"), + match("python-binary", "libpython3.11.so.1.0"), + match("python-binary-lib", "libpython3.11.so.1.0"), + }, + }, + }, + }, + { + name: "positive-python-3.9-from-shared-redhat-lib", + fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-shared-lib-redhat-3.9", + 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"), + match("python-binary-lib", "libpython3.9.so.1.0"), + }, + }, + }, + }, + { + name: "positive-python-binary-with-version-3.9", + fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-with-version-3.9", + 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"), + }, + }, + }, + }, + { + name: "positive-python-binary-3.4-alpine", + fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-3.4-alpine", + 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"), + match("python-binary-lib", "libpython3.4m.so.1.0"), + }, + }, + }, + }, + { + name: "positive-ruby-3.2.1", + fixtureDir: "test-fixtures/classifiers/dynamic/ruby-library-3.2.1", + 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"), + }, + }, + }, + }, + { + name: "positive-ruby-2.7.7", + fixtureDir: "test-fixtures/classifiers/dynamic/ruby-library-2.7.7", + 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"), + }, + }, + }, + }, + { + name: "positive-ruby-2.6.10", + fixtureDir: "test-fixtures/classifiers/dynamic/ruby-library-2.6.10", + 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"), + }, + }, + }, + }, + { + name: "positive-consul-1.15.2", + fixtureDir: "test-fixtures/classifiers/dynamic/consul-1.15.2", + 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"), + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + c := NewCataloger() + + src, err := source.NewFromDirectoryPath(test.fixtureDir) + require.NoError(t, err) + + resolver, err := src.FileResolver(source.SquashedScope) + require.NoError(t, err) + + packages, _, err := c.Catalog(resolver) + require.NoError(t, err) + + require.Len(t, packages, 1) + + assertPackagesAreEqual(t, test.expected, packages[0]) + }) + } +} + +func Test_Cataloger_DefaultClassifiers_PositiveCaseContents(t *testing.T) { + tests := []struct { + name string + fixtureDir string + contents []byte + fileGlob string + expected pkg.Package + }{ + { + name: "positive-postgresql-15beta4", + // note [NUL] prefix is important for the EvidenceMatcher in this case + contents: []byte("\u0000PostgreSQL 15beta4\n"), + fileGlob: "postgres", expected: pkg.Package{ Name: "postgresql", Version: "15beta4", @@ -37,8 +223,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-postgresql-15.1", - fixtureDir: "test-fixtures/classifiers/positive/postgresql-15.1", + name: "positive-postgresql-15.1", + contents: []byte("\u0000PostgreSQL 15.1\n"), + fileGlob: "postgres", expected: pkg.Package{ Name: "postgresql", Version: "15.1", @@ -49,8 +236,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-postgresql-9.6.24", - fixtureDir: "test-fixtures/classifiers/positive/postgresql-9.6.24", + name: "positive-postgresql-9.6.24", + contents: []byte("\u0000PostgreSQL 9.6.24\n"), + fileGlob: "postgres", expected: pkg.Package{ Name: "postgresql", Version: "9.6.24", @@ -61,8 +249,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-postgresql-9.5alpha1", - fixtureDir: "test-fixtures/classifiers/positive/postgresql-9.5alpha1", + name: "positive-postgresql-9.5alpha1", + contents: []byte("\u0000PostgreSQL 9.5alpha1\n"), + fileGlob: "postgres", expected: pkg.Package{ Name: "postgresql", Version: "9.5alpha1", @@ -73,8 +262,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-mysql-8.0.34", - fixtureDir: "test-fixtures/classifiers/positive/mysql-8.0.34", + name: "positive-mysql-8.0.34", + contents: []byte(" is already loaded\u0000../../mysql-8.0.34/sql-common/client_plugin.cc"), + fileGlob: "mysql", expected: pkg.Package{ Name: "mysql", Version: "8.0.34", @@ -85,8 +275,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-mysql-5.6.51", - fixtureDir: "test-fixtures/classifiers/positive/mysql-5.6.51", + name: "positive-mysql-5.6.51", + contents: []byte("-backup-restorer-mysql-5.6/mysql-5.6.51/client/completion_hash.cc\u0000/var/vcap/data/compile/database-ba\n"), + fileGlob: "mysql", expected: pkg.Package{ Name: "mysql", Version: "5.6.51", @@ -97,8 +288,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-mariadb-10.6.15", - fixtureDir: "test-fixtures/classifiers/positive/mariadb-10.6.15", + name: "positive-mariadb-10.6.15", + contents: []byte(")\u0000Linux\u000010.6.15-MariaDB\u0000readline\u0000x86_64\u0000\n"), + fileGlob: "mariadb", expected: pkg.Package{ Name: "mariadb", Version: "10.6.15", @@ -109,8 +301,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-traefik-2.9.6", - fixtureDir: "test-fixtures/classifiers/positive/traefik-2.9.6", + name: "positive-traefik-2.9.6", + contents: []byte("\u00002.9.6\u0000\n"), + fileGlob: "traefik", expected: pkg.Package{ Name: "traefik", Version: "2.9.6", @@ -121,8 +314,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-traefik-1.7.34", - fixtureDir: "test-fixtures/classifiers/positive/traefik-1.7.34", + name: "positive-traefik-1.7.34", + contents: []byte("\u0000v1.7.34\u0000\n"), + fileGlob: "traefik", expected: pkg.Package{ Name: "traefik", Version: "1.7.34", @@ -133,8 +327,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-memcached-1.6.18", - fixtureDir: "test-fixtures/classifiers/positive/memcached-1.6.18", + name: "positive-memcached-1.6.18", + contents: []byte("memcached 1.6.18\nudp-port\nmemcached 1.6.18\nFailed to allocate memory\nVERSION 1.6.18\nquit\n"), + fileGlob: "memcached", expected: pkg.Package{ Name: "memcached", Version: "1.6.18", @@ -145,8 +340,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-httpd-2.4.54", - fixtureDir: "test-fixtures/classifiers/positive/httpd-2.4.54", + name: "positive-httpd-2.4.54", + contents: []byte("\n"), + fileGlob: "haproxy", expected: pkg.Package{ Name: "haproxy", Version: "1.5.14", @@ -241,8 +444,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-haproxy-1.8.22", - fixtureDir: "test-fixtures/classifiers/positive/haproxy-1.8.22", + name: "positive-haproxy-1.8.22", + contents: []byte("\u0000HA-Proxy version 1.8.22 2019/10/25\u0000Copyright 2000-2019 Willy Tarreau \n"), + fileGlob: "haproxy", expected: pkg.Package{ Name: "haproxy", Version: "1.8.22", @@ -253,8 +457,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-haproxy-2.7.3", - fixtureDir: "test-fixtures/classifiers/positive/haproxy-2.7.3", + name: "positive-haproxy-2.7.3", + contents: []byte("XZ\u0000\u0000 version 2.7.3-1065b10, released 2023/02/14u0000\u0000\u00002.7.3-1065b10HAProxy version follows\u0000\u0000\n"), + fileGlob: "haproxy", expected: pkg.Package{ Name: "haproxy", Version: "2.7.3", @@ -265,8 +470,10 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-redis-2.8.23", - fixtureDir: "test-fixtures/classifiers/positive/redis-server-2.8.23", + name: "positive-redis-2.8.23", + // note the extraction after payload and the \u000 is important for this case and finding 2.8.23 + contents: []byte("pl: %5u, pls: %2u, payload %5u} \u000000000000\u0000\u0000\u0000\u0000\u0000\u0000\u00002.8.231640342e135b-1465416213000000000"), + fileGlob: "redis-server", expected: pkg.Package{ Name: "redis", Version: "2.8.23", @@ -276,33 +483,11 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Metadata: metadata("redis-binary"), }, }, + { - name: "positive-helm-3.11.1", - fixtureDir: "test-fixtures/classifiers/dynamic/helm-3.11.1", - 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"), - }, - }, - { - name: "positive-helm-3.10.3", - fixtureDir: "test-fixtures/classifiers/dynamic/helm-3.10.3", - 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"), - }, - }, - { - name: "positive-redis-4.0.11", - fixtureDir: "test-fixtures/classifiers/positive/redis-server-4.0.11", + name: "positive-redis-4.0.11", + contents: []byte("\tpayload %5u\n\u0000ziplist.c\u00004.0.11841ce7054bd9-1542359302000000000\u0000"), + fileGlob: "redis-server", expected: pkg.Package{ Name: "redis", Version: "4.0.11", @@ -313,8 +498,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-redis-5.0.0", - fixtureDir: "test-fixtures/classifiers/positive/redis-server-5.0.0", + name: "positive-redis-5.0.0", + contents: []byte("\tpayload %5u\n\u0000ziplist.c\u0000prevlen\u00005.0.05ca5019de136-1539906480000000000\u0000"), + fileGlob: "redis-server", expected: pkg.Package{ Name: "redis", Version: "5.0.0", @@ -325,8 +511,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-redis-6.0.16", - fixtureDir: "test-fixtures/classifiers/positive/redis-server-6.0.16", + name: "positive-redis-6.0.16", + contents: []byte("\tpayload %5u\n\u000000000000\u0000%llx\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u00006.0.16f823c11b1f9e-1671626578000000000\u0000networking.c\n"), + fileGlob: "redis-server", expected: pkg.Package{ Name: "redis", Version: "6.0.16", @@ -338,6 +525,8 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, { name: "positive-redis-7.0.0", + contents: []byte("\tpayload %5u\n0000\u0000\u00007.0.0e11381fbd8bd-1653733480000000000\u0000CACHING (YES|NO)\n"), + fileGlob: "redis-server", fixtureDir: "test-fixtures/classifiers/positive/redis-server-7.0.0", expected: pkg.Package{ Name: "redis", @@ -349,8 +538,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-redis-7.0.14", - fixtureDir: "test-fixtures/classifiers/positive/redis-server-7.0.14", + name: "positive-redis-7.0.14", + contents: []byte("\tpayload %5u\n\u0000\u0000\u0000\u0000\u0000\u0000ziplistGet(p, &value, &vlen, &vlval)\u0000\u00007.0.14buildkitsandbox-1698800572000000000\u0000\n"), + fileGlob: "redis-server", expected: pkg.Package{ Name: "redis", Version: "7.0.14", @@ -361,20 +551,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-redis-7.2.3-amd64", - fixtureDir: "test-fixtures/classifiers/positive/redis-server-7.2.3-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"), - }, - }, - { - name: "positive-redis-7.2.3-arm64", - fixtureDir: "test-fixtures/classifiers/positive/redis-server-7.2.3-arm64", + name: "positive-redis-7.2.3-amd64", + contents: []byte("\u00007.2.3707d15b3058f-1698842949000000000\u0000\u0000\u0000zipEntrySafe(zl, zlbytes, p, &e, 0)\tpayload %5u\n"), + fileGlob: "redis-server", expected: pkg.Package{ Name: "redis", Version: "7.2.3", @@ -385,8 +564,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-libpython3.7.so", - fixtureDir: "test-fixtures/classifiers/positive/python-binary-lib-3.7", + name: "positive-libpython3.7.so", + contents: []byte("r\u0000python3.9\u0000PYTHONIOENCODING\u00003.7.4\u0000/lib/pythonX.X\u0000Python %s\n\u0000\n"), + fileGlob: "libpython3.7.so", expected: pkg.Package{ Name: "python", Version: "3.7.4", @@ -395,74 +575,11 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Metadata: metadata("python-binary-lib"), }, }, - { - name: "positive-python-3.11.2-from-shared-lib", - fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-shared-lib-3.11", - expected: pkg.Package{ - Name: "python", - Version: "3.11.2", - PURL: "pkg:generic/python@3.11.2", - Locations: locations("python3", "libpython3.11.so.1.0"), - Metadata: pkg.BinarySignature{ - Matches: []pkg.ClassifierMatch{ - match("python-binary", "python3"), - match("python-binary", "libpython3.11.so.1.0"), - match("python-binary-lib", "libpython3.11.so.1.0"), - }, - }, - }, - }, - { - name: "positive-python-3.9-from-shared-redhat-lib", - fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-shared-lib-redhat-3.9", - 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"), - match("python-binary-lib", "libpython3.9.so.1.0"), - }, - }, - }, - }, - { - name: "positive-python-binary-with-version-3.9", - fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-with-version-3.9", - 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"), - }, - }, - }, - }, - { - name: "positive-python-binary-3.4-alpine", - fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-3.4-alpine", - 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"), - match("python-binary-lib", "libpython3.4m.so.1.0"), - }, - }, - }, - }, + { name: "positive-python-3.5-with-incorrect-match", + contents: []byte("\u0000305\u0000path\u0000path_importer_cache\u0000must be %.50s, not %.50s\u00003.5.3\u0000%.80s (%.80s) %.\n"), + fileGlob: "python3.5", fixtureDir: "test-fixtures/classifiers/positive/python-3.5-with-incorrect-match", expected: pkg.Package{ Name: "python", @@ -473,8 +590,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-python3.6", - fixtureDir: "test-fixtures/classifiers/positive/python-binary-3.6", + name: "positive-python3.6", + contents: []byte("r\u0000python3.9\u0000PYTHONIOENCODING\u00003.6.3\u0000/lib/pythonX.X\u0000Python %s\n"), + fileGlob: "python3.6", expected: pkg.Package{ Name: "python", Version: "3.6.3", @@ -483,27 +601,13 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Metadata: metadata("python-binary"), }, }, + { - name: "positive-python-duplicates", - fixtureDir: "test-fixtures/classifiers/positive/python-duplicates", - 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"), - }, - }, - }, - }, - { - name: "positive-go", - fixtureDir: "test-fixtures/classifiers/positive/go-1.14", + name: "positive-go", + // Note the \x00 is important for this case, as it is a null byte that is used to terminate the string + // this is expected for a positive match on the Golang executable + contents: []byte(strings.Join([]string{`"go1.1"`, "go1.2", "go1.14\x00"}, "\n")), + fileGlob: "go", expected: pkg.Package{ Name: "go", Version: "1.14", @@ -513,8 +617,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-node", - fixtureDir: "test-fixtures/classifiers/positive/node-19.2.1", + name: "positive-node", + contents: []byte("# this should match node 19.2.1\nnode.js/v19.2.1\n"), + fileGlob: "node", expected: pkg.Package{ Name: "node", Version: "19.2.1", @@ -524,8 +629,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-go-hint", - fixtureDir: "test-fixtures/classifiers/positive/go-hint-1.15", + name: "positive-go-hint", + contents: []byte("go1.15-beta2\n"), + fileGlob: "VERSION", expected: pkg.Package{ Name: "go", Version: "1.15", @@ -534,19 +640,11 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Metadata: metadata("go-binary-hint"), }, }, + { - name: "positive-busybox", - fixtureDir: "test-fixtures/classifiers/positive/busybox-3.33.3", - expected: pkg.Package{ - Name: "busybox", - Version: "3.33.3", - Locations: locations("["), // note: busybox is a link to [ - Metadata: metadata("busybox-binary", "[", "busybox"), - }, - }, - { - name: "positive-java-openjdk", - fixtureDir: "test-fixtures/classifiers/positive/openjdk", + name: "positive-java-openjdk", + contents: []byte("\u0000\u0000\u0001\u0000\u0002\u0000openjdk\u0000java\u00001.8\u00001.8.0_352-b08\u0000\u0000\u0001\n"), + fileGlob: "java", expected: pkg.Package{ Name: "java", Version: "1.8.0_352-b08", @@ -557,8 +655,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-java-openjdk-lts", - fixtureDir: "test-fixtures/classifiers/positive/openjdk-lts", + name: "positive-java-openjdk-lts", + contents: []byte("# java LTS pattern\nJDK_JAVA_OPTIONS\u0000_JAVA_LAUNCHER_DEBUG\u0000NOTE: Picked up %s: %s\u0000openjdk\u0000java\u00000.0\u000011.0.17+8-LTS\u0000-J-ms8m\u0000\u0001\u001B\u0003;\n"), + fileGlob: "java", expected: pkg.Package{ Name: "java", Version: "11.0.17+8-LTS", @@ -569,8 +668,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-java-oracle", - fixtureDir: "test-fixtures/classifiers/positive/oracle", + name: "positive-java-oracle", + contents: []byte("#this should be an oracle java binary\nwith: \u0000java\u00000.0\u000019.0.1+10-21\u0000\nand 18.0.2 has nothing to do with the version\n"), + fileGlob: "java", expected: pkg.Package{ Name: "java", Version: "19.0.1+10-21", @@ -581,8 +681,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-java-oracle-macos", - fixtureDir: "test-fixtures/classifiers/positive/oracle-macos", + name: "positive-java-oracle-macos", + contents: []byte("#oracle macos\nis different: \u000019.0.1+10-21\u00000.0\u0000java\u0000\nthis should not be 17.2.2\n"), + fileGlob: "java", expected: pkg.Package{ Name: "java", Version: "19.0.1+10-21", @@ -593,8 +694,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-java-ibm", - fixtureDir: "test-fixtures/classifiers/positive/ibm", + name: "positive-java-ibm", + contents: []byte("# this is an ibm java\n\u0001\u0000\u0002\u0000java\u00001.8\u0000\u0000\u0000\u00001.8.0-foreman_2022_09_22_15_30-b00\u0000\u0000\u0000\u0000\u0000\u0000\u0001\u001B\u0003;4\u0000\u0000\u0000\u0005\u0000\ntype of file 1.9.0 1.8.99\n"), + fileGlob: "java", expected: pkg.Package{ Name: "java", Version: "1.8.0-foreman_2022_09_22_15_30-b00", @@ -605,95 +707,48 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-rust-1.50.0-macos", - fixtureDir: "test-fixtures/classifiers/positive/rust-1.50.0", + name: "positive-rust-1.50.0-macos", + contents: []byte("rust\u0000\u0000\u0000\u0005\u0000X¥g#\u0001\n\uF8FFÜc 1.50.0 (cb75ad5db 2021-02-10)\u0004coreÖ∫¡°é¿»üé\u0001\u0000\u0002\u0011-59ed52fd3946b1c5\u0011compiler_builtins"), + fileGlob: "libstd-f6f9eec1635e636a.dylib", expected: pkg.Package{ Name: "rust", Version: "1.50.0", Type: "binary", PURL: "pkg:generic/rust@1.50.0", - Locations: locations("lib/rustlib/aarch64-apple-darwin/lib/libstd-f6f9eec1635e636a.dylib"), + Locations: locations("libstd-f6f9eec1635e636a.dylib"), Metadata: metadata("rust-standard-library-macos"), }, }, { - name: "positive-rust-1.67.1-macos", - fixtureDir: "test-fixtures/classifiers/positive/rust-1.67.1/toolchains/stable-aarch64-apple-darwin", + name: "positive-rust-1.67.1-macos", + contents: []byte("rust\u0000\u0000\u0000\u0006\u0000\u0089°·#\u0001\nðec 1.67.1 (d5a82bbd2 2023-02-07)Á\u0002á\u0003\u009AØ\u0098\u0089\u0080ïß\u0097»\u0001\u0000\u0002\u0011-33fcb3a02520939aÁ\n"), + fileGlob: "libstd-16f2b65e77054c42.dylib", expected: pkg.Package{ Name: "rust", Version: "1.67.1", Type: "binary", PURL: "pkg:generic/rust@1.67.1", - Locations: locations("lib/libstd-16f2b65e77054c42.dylib"), + Locations: locations("libstd-16f2b65e77054c42.dylib"), Metadata: metadata("rust-standard-library-macos"), }, }, { - name: "positive-rust-1.67.1-linux", - fixtureDir: "test-fixtures/classifiers/positive/rust-1.67.1/toolchains/stable-x86_64-unknown-linux-musl", + name: "positive-rust-1.67.1-linux", + contents: []byte("obj_musl\u0000GNU AS 2.33.1\u0000clang LLVM (rustc version 1.67.1 (d5a82bbd2 2023-02-07))\u0000library/std/src/lib.rs/@/std.d836545c-cgu.0\n"), + fileGlob: "libstd-86aefecbddda356d.so", expected: pkg.Package{ Name: "rust", Version: "1.67.1", Type: "binary", PURL: "pkg:generic/rust@1.67.1", - Locations: locations("lib/libstd-86aefecbddda356d.so"), + Locations: locations("libstd-86aefecbddda356d.so"), Metadata: metadata("rust-standard-library-linux"), }, }, { - name: "positive-ruby-3.2.1", - fixtureDir: "test-fixtures/classifiers/dynamic/ruby-library-3.2.1", - 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"), - }, - }, - }, - }, - { - name: "positive-ruby-2.7.7", - fixtureDir: "test-fixtures/classifiers/dynamic/ruby-library-2.7.7", - 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"), - }, - }, - }, - }, - { - name: "positive-ruby-2.6.10", - fixtureDir: "test-fixtures/classifiers/dynamic/ruby-library-2.6.10", - 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"), - }, - }, - }, - }, - { - name: "positive-ruby-1.9.3p551", - fixtureDir: "test-fixtures/classifiers/positive/ruby-1.9.3p551", + name: "positive-ruby-1.9.3p551", + contents: []byte("ruby 1.9.3p551 (2014-11-13 revision 48407) [x86_64-linux]\n"), + fileGlob: "ruby", expected: pkg.Package{ Name: "ruby", Version: "1.9.3p551", @@ -704,20 +759,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-consul-1.15.2", - fixtureDir: "test-fixtures/classifiers/dynamic/consul-1.15.2", - 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"), - }, - }, - { - name: "positive-nginx-1.25.1", - fixtureDir: "test-fixtures/classifiers/positive/nginx-1.25.1", + name: "positive-nginx-1.25.1", + contents: []byte("\u0000\u0000nginx version: nginx/1.25.1\n"), + fileGlob: "nginx", expected: pkg.Package{ Name: "nginx", Version: "1.25.1", @@ -728,8 +772,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-nginx-openresty-1.21.4.2", - fixtureDir: "test-fixtures/classifiers/positive/nginx-openresty-1.21.4.2", + name: "positive-nginx-openresty-1.21.4.2", + contents: []byte("\u0000\u0000nginx version: openresty/1.21.4.2\n"), + fileGlob: "nginx", expected: pkg.Package{ Name: "nginx", Version: "1.21.4", @@ -740,8 +785,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { }, }, { - name: "positive-bash-5.2.15", - fixtureDir: "test-fixtures/classifiers/positive/bash-5.2.15", + name: "positive-bash-5.2.15", + contents: []byte("@(#)Bash version 5.2.15(1) release GNU\n"), + fileGlob: "bash", expected: pkg.Package{ Name: "bash", Version: "5.2.15", @@ -751,13 +797,28 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Metadata: metadata("bash-binary"), }, }, + { + name: "positive-openssl-3.1.4", + contents: []byte("\u0000\u0000N/A\u0000\u0000\u0000\u0000\u0000OpenSSL 3.1.4 24 Oct 2023\u0000\u0000\n"), + fileGlob: "openssl", + 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"), + }, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { + dir, cleanup, err := newFixtureFromBytes(test.contents, test.name, test.fileGlob) + require.NoError(t, err) + defer cleanup() c := NewCataloger() - - src, err := source.NewFromDirectoryPath(test.fixtureDir) + src, err := source.NewFromDirectoryPath(dir) require.NoError(t, err) resolver, err := src.FileResolver(source.SquashedScope) @@ -773,6 +834,68 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { } } +func Test_Cataloger_DefaultClassifiers_PositiveComplexCase(t *testing.T) { + tests := []struct { + name string + fixtureDir string + expected pkg.Package + }{ + { + name: "positive-python-duplicates regression", + fixtureDir: "test-fixtures/classifiers/python-duplicates", + 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"), + Metadata: pkg.BinarySignature{ + Matches: []pkg.ClassifierMatch{ + match("python-binary", "dir/python3.8"), + match("python-binary", "python3.8"), + }, + }, + }, + }, + } + + for _, test := range tests { + c := NewCataloger() + + src, err := source.NewFromDirectoryPath(test.fixtureDir) + require.NoError(t, err) + + resolver, err := src.FileResolver(source.SquashedScope) + require.NoError(t, err) + + packages, _, err := c.Catalog(resolver) + require.NoError(t, err) + + require.Len(t, packages, 1) + + assertPackagesAreEqual(t, test.expected, packages[0]) + } +} + +func newFixtureFromBytes(contents []byte, testName, fileGlob string) (string, func(), error) { + tempDir, err := os.MkdirTemp("", testName) + if err != nil { + return "", nil, err + } + + cleanup := func() { + os.RemoveAll(tempDir) + } + + err = os.WriteFile(filepath.Join(tempDir, fileGlob), contents, 0644) + if err != nil { + return "", cleanup, err + } + + return tempDir, cleanup, nil + +} + func Test_Cataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T) { tests := []struct { name string diff --git a/syft/pkg/cataloger/binary/default_classifiers.go b/syft/pkg/cataloger/binary/default_classifiers.go index 88e16454e53..3843e9c5568 100644 --- a/syft/pkg/cataloger/binary/default_classifiers.go +++ b/syft/pkg/cataloger/binary/default_classifiers.go @@ -305,6 +305,17 @@ var defaultClassifiers = []classifier{ PURL: mustPURL("pkg:generic/bash@version"), CPEs: singleCPE("cpe:2.3:a:gnu:bash:*:*:*:*:*:*:*:*"), }, + { + Class: "openssl-binary", + FileGlob: "**/openssl", + EvidenceMatcher: fileContentsVersionMatcher( + // [NUL]OpenSSL 3.1.4' + `\x00OpenSSL (?P[0-9]+\.[0-9]+\.[0-9]+(-alpha[0-9]|-beta[0-9]|-rc[0-9])?)`, + ), + Package: "openssl", + PURL: mustPURL("pkg:generic/openssl@version"), + CPEs: singleCPE("cpe:2.3:a:openssl:openssl:*:*:*:*:*:*:*:*"), + }, } // in both binaries and shared libraries, the version pattern is [NUL]3.11.2[NUL] diff --git a/syft/pkg/cataloger/binary/test-fixtures/README.md b/syft/pkg/cataloger/binary/test-fixtures/README.md new file mode 100644 index 00000000000..549c3f5cf6b --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/README.md @@ -0,0 +1,117 @@ +## Evidence details for `binary` cataloger content matching + +### `original-mariadb` +The binary snippet was gathered with: + +```bash +$ cat ./original-mariadb | strings | grep '-MariaDB' +# assert you can see the value + + +$ xxd ./original-mariadb | grep '\-MariaDB' +# get the address... + + +$ xxd -s 0x003dd5c0 -l 40 ./original-mariadb + +003dd5c0: 2900 4c69 6e75 7800 3130 2e36 2e31 352d ).Linux.10.6.15- +003dd5d0: 4d61 7269 6144 4200 7265 6164 6c69 6e65 MariaDB.readline +003dd5e0: 0078 3836 5f36 3400 .x86_64. + + +$ dd if=./original-mariadb of=mariadb bs=1 skip=$((0x003dd5c0)) count=40 + +40+0 records in +40+0 records out +40 bytes transferred in 0.000264 secs (151515 bytes/sec) + + +$ xxd mariadb + +00000000: 2900 4c69 6e75 7800 3130 2e36 2e31 352d ).Linux.10.6.15- +00000010: 4d61 7269 6144 4200 7265 6164 6c69 6e65 MariaDB.readline +00000020: 0078 3836 5f36 3400 .x86_64. +``` + +### `original-mysql 5.6.51` +The binary snippet was gathered with: + +```bash +$ cat ./original-mysql | strings | grep '5.6.51' +# assert you can see the value + + +$ xxd ./original-mysql | grep '5.6.51' +# get the address... + + +$ xxd -s 0x008f13d0 -l 100 original-mysql + +008f13d0: 2d62 6163 6b75 702d 7265 7374 6f72 6572 -backup-restorer +008f13e0: 2d6d 7973 716c 2d35 2e36 2f6d 7973 716c -mysql-5.6/mysql +008f13f0: 2d35 2e36 2e35 312f 636c 6965 6e74 2f63 -5.6.51/client/c +008f1400: 6f6d 706c 6574 696f 6e5f 6861 7368 2e63 ompletion_hash.c +008f1410: 6300 2f76 6172 2f76 6361 702f 6461 7461 c./var/vcap/data +008f1420: 2f63 6f6d 7069 6c65 2f64 6174 6162 6173 /compile/databas +008f1430: 652d 6261 e-ba + + +$ dd if=./original-mysql of=mysql bs=1 skip=$((0x008f13d0)) count=100 + +100+0 records in +100+0 records out +100 bytes transferred in 0.000642 secs (155763 bytes/sec) + + +$ xxd mysql + +00000000: 2d62 6163 6b75 702d 7265 7374 6f72 6572 -backup-restorer +00000010: 2d6d 7973 716c 2d35 2e36 2f6d 7973 716c -mysql-5.6/mysql +00000020: 2d35 2e36 2e35 312f 636c 6965 6e74 2f63 -5.6.51/client/c +00000030: 6f6d 706c 6574 696f 6e5f 6861 7368 2e63 ompletion_hash.c +00000040: 6300 2f76 6172 2f76 6361 702f 6461 7461 c./var/vcap/data +00000050: 2f63 6f6d 7069 6c65 2f64 6174 6162 6173 /compile/databas +00000060: 652d 6261 e-ba +``` + + +### `original-mysql-8` +The binary snippet was gathered with: +```bash +$ cat ./original-mysql | strings | grep '8.0.34' +# assert you can see the value + + +$ xxd ./original-mysql | grep '8.0.34' +# get the address... + + +$ xxd -s 0x0014cd20 -l 100 original-mysql + +0014cd20: 2069 7320 616c 7265 6164 7920 6c6f 6164 is already load +0014cd30: 6564 0000 0000 0000 2e2e 2f2e 2e2f 6d79 ed......../../my +0014cd40: 7371 6c2d 382e 302e 3334 2f73 716c 2d63 sql-8.0.34/sql-c +0014cd50: 6f6d 6d6f 6e2f 636c 6965 6e74 5f70 6c75 ommon/client_plu +0014cd60: 6769 6e2e 6363 002f 7573 722f 6c6f 6361 gin.cc./usr/loca +0014cd70: 6c2f 6d79 7371 6c2f 6c69 622f 706c 7567 l/mysql/lib/plug +0014cd80: 696e 0049 in.I + + + +$ dd if=./original-mysql of=mysql bs=1 skip=$((0x0014cd20)) count=100 + +100+0 records in +100+0 records out +100 bytes transferred in 0.000519 secs (192678 bytes/sec) + + +$ xxd mysql + +00000000: 2069 7320 616c 7265 6164 7920 6c6f 6164 is already load +00000010: 6564 0000 0000 0000 2e2e 2f2e 2e2f 6d79 ed......../../my +00000020: 7371 6c2d 382e 302e 3334 2f73 716c 2d63 sql-8.0.34/sql-c +00000030: 6f6d 6d6f 6e2f 636c 6965 6e74 5f70 6c75 ommon/client_plu +00000040: 6769 6e2e 6363 002f 7573 722f 6c6f 6361 gin.cc./usr/loca +00000050: 6c2f 6d79 7371 6c2f 6c69 622f 706c 7567 l/mysql/lib/plug +00000060: 696e 0049 in.I +``` \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/.gitignore b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/.gitignore deleted file mode 100644 index 5acc24bd654..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -!*.so -!VERSION \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/bash-5.2.15/bash b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/bash-5.2.15/bash deleted file mode 100644 index 17c8fc2a020..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/bash-5.2.15/bash +++ /dev/null @@ -1 +0,0 @@ -@(#)Bash version 5.2.15(1) release GNU diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/busybox-3.33.3/[ b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/busybox-3.33.3/[ deleted file mode 100644 index 7829d71b941..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/busybox-3.33.3/[ +++ /dev/null @@ -1,3 +0,0 @@ -# note: this SHOULD match as busybox 3.33.3 - -noise!BusyBox v3.33.3!noise \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/busybox-3.33.3/busybox b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/busybox-3.33.3/busybox deleted file mode 120000 index c3e3150b864..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/busybox-3.33.3/busybox +++ /dev/null @@ -1 +0,0 @@ -./[ \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-1.14/go b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-1.14/go deleted file mode 100644 index f18b07b8a2b..00000000000 Binary files a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-1.14/go and /dev/null differ diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-hint-1.15/VERSION b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-hint-1.15/VERSION deleted file mode 100644 index 5bedbed9f95..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/go-hint-1.15/VERSION +++ /dev/null @@ -1 +0,0 @@ -go1.15-beta2 \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.5.14/haproxy b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.5.14/haproxy deleted file mode 100644 index 7fc2c2a35b4..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.5.14/haproxy +++ /dev/null @@ -1 +0,0 @@ -HA-Proxy version 1.5.14 2015/07/02Copyright 2000-2015 Willy Tarreau diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.8.22/haproxy b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.8.22/haproxy deleted file mode 100644 index a424ab0ac66..00000000000 Binary files a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-1.8.22/haproxy and /dev/null differ diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-2.7.3/haproxy b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-2.7.3/haproxy deleted file mode 100644 index da21a03ddc8..00000000000 Binary files a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/haproxy-2.7.3/haproxy and /dev/null differ diff --git a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/httpd-2.4.54/httpd b/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/httpd-2.4.54/httpd deleted file mode 100644 index 441662ab1a6..00000000000 --- a/syft/pkg/cataloger/binary/test-fixtures/classifiers/positive/httpd-2.4.54/httpd +++ /dev/null @@ -1,18 +0,0 @@ -