fix: align binary java detection with jvm cataloger + support IBM (#4046)

Signed-off-by: Keith Zantow <kzantow@gmail.com>
This commit is contained in:
Keith Zantow 2025-07-22 12:06:32 -04:00 committed by GitHub
parent 78c7cd2cc2
commit 48bf81cf7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 612 additions and 166 deletions

View File

@ -41,11 +41,11 @@ func MatchNamedCaptureGroups(regEx *regexp.Regexp, content string) map[string]st
// 1.5x the reader chunk size (1MB * 1.5). // 1.5x the reader chunk size (1MB * 1.5).
func MatchNamedCaptureGroupsFromReader(re *regexp.Regexp, r io.Reader) (map[string]string, error) { func MatchNamedCaptureGroupsFromReader(re *regexp.Regexp, r io.Reader) (map[string]string, error) {
results := make(map[string]string) results := make(map[string]string)
_, err := processReaderInChunks(r, readerChunkSize, matchNamedCaptureGroupsHandler(re, results)) matches, err := processReaderInChunks(r, readerChunkSize, matchNamedCaptureGroupsHandler(re, results))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(results) == 0 { if !matches {
return nil, nil return nil, nil
} }
return results, nil return results, nil

View File

@ -697,21 +697,21 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
{ {
logicalFixture: "java-jre-openjdk/1.8.0_352-b08/linux-amd64", logicalFixture: "java-jre-openjdk/1.8.0_352-b08/linux-amd64",
expected: pkg.Package{ expected: pkg.Package{
Name: "java/jre", Name: "openjdk",
Version: "1.8.0_352-b08", Version: "1.8.0_352-b08",
Type: "binary", Type: "binary",
PURL: "pkg:generic/java/jre@1.8.0_352-b08", PURL: "pkg:generic/oracle/openjdk@1.8.0_352-b08",
Locations: locations("java"), Locations: locations("java"),
Metadata: metadata("java-binary-openjdk", "java"), Metadata: metadata("java-binary-openjdk-with-update", "java"),
}, },
}, },
{ {
logicalFixture: "java-jre-openjdk/11.0.17/linux-amd64", logicalFixture: "java-jre-openjdk/11.0.17/linux-amd64",
expected: pkg.Package{ expected: pkg.Package{
Name: "java/jre", Name: "openjdk",
Version: "11.0.17+8-LTS", Version: "11.0.17+8-LTS",
Type: "binary", Type: "binary",
PURL: "pkg:generic/java/jre@11.0.17%2B8-LTS", PURL: "pkg:generic/oracle/openjdk@11.0.17%2B8-LTS",
Locations: locations("java"), Locations: locations("java"),
Metadata: metadata("java-binary-openjdk", "java"), Metadata: metadata("java-binary-openjdk", "java"),
}, },
@ -719,10 +719,10 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
{ {
logicalFixture: "java-jre-openjdk-eclipse/11.0.22/linux-amd64", logicalFixture: "java-jre-openjdk-eclipse/11.0.22/linux-amd64",
expected: pkg.Package{ expected: pkg.Package{
Name: "java/jre", Name: "openjdk",
Version: "11.0.22+7", Version: "11.0.22+7",
Type: "binary", Type: "binary",
PURL: "pkg:generic/java/jre@11.0.22%2B7", PURL: "pkg:generic/oracle/openjdk@11.0.22%2B7",
Locations: locations("java"), Locations: locations("java"),
Metadata: metadata("java-binary-openjdk", "java"), Metadata: metadata("java-binary-openjdk", "java"),
}, },
@ -730,10 +730,10 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
{ {
logicalFixture: "java-jre-openjdk-arm64-eclipse/11.0.22/linux-arm64", logicalFixture: "java-jre-openjdk-arm64-eclipse/11.0.22/linux-arm64",
expected: pkg.Package{ expected: pkg.Package{
Name: "java/jre", Name: "openjdk",
Version: "11.0.22+7", Version: "11.0.22+7",
Type: "binary", Type: "binary",
PURL: "pkg:generic/java/jre@11.0.22%2B7", PURL: "pkg:generic/oracle/openjdk@11.0.22%2B7",
Locations: locations("java"), Locations: locations("java"),
Metadata: metadata("java-binary-openjdk", "java"), Metadata: metadata("java-binary-openjdk", "java"),
}, },
@ -741,10 +741,10 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
{ {
logicalFixture: "java-graal-openjdk/17.0.3+7-jvmci-22.1-b06/linux-amd64", logicalFixture: "java-graal-openjdk/17.0.3+7-jvmci-22.1-b06/linux-amd64",
expected: pkg.Package{ expected: pkg.Package{
Name: "java/graalvm", Name: "graalvm",
Version: "17.0.3+7-jvmci-22.1-b06", Version: "17.0.3+7-jvmci-22.1-b06",
Type: "binary", Type: "binary",
PURL: "pkg:generic/java/graalvm@17.0.3%2B7-jvmci-22.1-b06", PURL: "pkg:generic/oracle/graalvm@17.0.3%2B7-jvmci-22.1-b06",
Locations: locations("java"), Locations: locations("java"),
Metadata: metadata("java-binary-graalvm", "java"), Metadata: metadata("java-binary-graalvm", "java"),
}, },
@ -754,10 +754,10 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
// note: cannot find the original binary, using a custom snippet based on the original snippet in the repo // 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", logicalFixture: "java-jre-oracle/19.0.1/linux-amd64",
expected: pkg.Package{ expected: pkg.Package{
Name: "java/jre", Name: "jre",
Version: "19.0.1+10-21", Version: "19.0.1+10-21",
Type: "binary", Type: "binary",
PURL: "pkg:generic/java/jre@19.0.1%2B10-21", PURL: "pkg:generic/oracle/jre@19.0.1%2B10-21",
Locations: locations("java"), Locations: locations("java"),
Metadata: metadata("java-binary-oracle", "java"), Metadata: metadata("java-binary-oracle", "java"),
}, },
@ -767,10 +767,10 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
// note: cannot find the original binary, using a custom snippet based on the original snippet in the repo // 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", logicalFixture: "java-jre-oracle/19.0.1/darwin",
expected: pkg.Package{ expected: pkg.Package{
Name: "java/jre", Name: "jre",
Version: "19.0.1+10-21", Version: "19.0.1+10-21",
Type: "binary", Type: "binary",
PURL: "pkg:generic/java/jre@19.0.1%2B10-21", PURL: "pkg:generic/oracle/jre@19.0.1%2B10-21",
Locations: locations("java"), Locations: locations("java"),
Metadata: metadata("java-binary-oracle", "java"), Metadata: metadata("java-binary-oracle", "java"),
}, },
@ -778,23 +778,45 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
{ {
logicalFixture: "java-jre-ibm/1.8.0_391/linux-amd64", logicalFixture: "java-jre-ibm/1.8.0_391/linux-amd64",
expected: pkg.Package{ expected: pkg.Package{
Name: "java/jre", Name: "java",
Version: "1.8.0-foreman_2023_10_12_13_27-b00", Version: "1.8.0-foreman_2023_10_12_13_27-b00",
Type: "binary", Type: "binary",
PURL: "pkg:generic/java/jre@1.8.0-foreman_2023_10_12_13_27-b00", PURL: "pkg:generic/ibm/java@1.8.0-foreman_2023_10_12_13_27-b00",
Locations: locations("java"), Locations: locations("java"),
Metadata: metadata("java-binary-ibm", "java"), Metadata: metadata("java-binary-ibm", "java"),
}, },
}, },
{
logicalFixture: "java-ibm-8-jre/1.8.0_451/linux-amd64",
expected: pkg.Package{
Name: "java",
Version: "1.8.0-_2025_04_14_02_37-b00",
Type: "binary",
PURL: "pkg:generic/ibm/java@1.8.0-_2025_04_14_02_37-b00",
Locations: locations("java"),
Metadata: metadata("java-binary-ibm", "java"),
},
},
{
logicalFixture: "java-ibm-8-sdk-alpine/1.8.0_321/linux-amd64",
expected: pkg.Package{
Name: "java_sdk",
Version: "1.8.0-foreman_2022_01_20_09_33-b00",
Type: "binary",
PURL: "pkg:generic/ibm/java_sdk@1.8.0-foreman_2022_01_20_09_33-b00",
Locations: locations("jdb"),
Metadata: metadata("java-sdk-binary-ibm", "jdb"),
},
},
{ {
logicalFixture: "java-jdk-openjdk/21.0.2+13-LTS/linux-amd64", logicalFixture: "java-jdk-openjdk/21.0.2+13-LTS/linux-amd64",
expected: pkg.Package{ expected: pkg.Package{
Name: "java/jdk", Name: "openjdk",
Version: "21.0.2+13-LTS", Version: "21.0.2+13-LTS",
Type: "binary", Type: "binary",
PURL: "pkg:generic/java/jdk@21.0.2%2B13-LTS", PURL: "pkg:generic/oracle/openjdk@21.0.2%2B13-LTS",
Locations: locations("jdb"), Locations: locations("jdb"),
Metadata: metadata("java-binary-jdk", "java"), Metadata: metadata("java-binary-openjdk-fallthrough", "jdb"),
}, },
}, },
{ {
@ -1463,8 +1485,8 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
Class: "foo-binary", Class: "foo-binary",
FileGlob: "**/foo", FileGlob: "**/foo",
EvidenceMatcher: binutils.FileContentsVersionMatcher( EvidenceMatcher: binutils.FileContentsVersionMatcher(
`(?m)foobar\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
catalogerName, catalogerName,
`(?m)foobar\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
), ),
Package: "foo", Package: "foo",
PURL: mustPURL("pkg:generic/foo@version"), PURL: mustPURL("pkg:generic/foo@version"),
@ -1521,7 +1543,7 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
binutils.Classifier{ binutils.Classifier{
Class: "foo-binary", Class: "foo-binary",
FileGlob: "**/foo", FileGlob: "**/foo",
EvidenceMatcher: binutils.FileContentsVersionMatcher(`(?m)not there`, catalogerName), EvidenceMatcher: binutils.FileContentsVersionMatcher(catalogerName, `(?m)not there`),
Package: "foo", Package: "foo",
PURL: mustPURL("pkg:generic/foo@version"), PURL: mustPURL("pkg:generic/foo@version"),
CPEs: singleCPE("cpe:2.3:a:foo:foo:*:*:*:*:*:*:*:*"), CPEs: singleCPE("cpe:2.3:a:foo:foo:*:*:*:*:*:*:*:*"),
@ -1739,7 +1761,7 @@ func TestCatalogerConfig_MarshalJSON(t *testing.T) {
{ {
Class: "class", Class: "class",
FileGlob: "glob", FileGlob: "glob",
EvidenceMatcher: binutils.FileContentsVersionMatcher(".thing", catalogerName), EvidenceMatcher: binutils.FileContentsVersionMatcher(catalogerName, ".thing"),
Package: "pkg", Package: "pkg",
PURL: packageurl.PackageURL{ PURL: packageurl.PackageURL{
Type: "type", Type: "type",

View File

@ -28,11 +28,11 @@ func DefaultClassifiers() []binutils.Classifier {
// ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5) [x86_64-linux] // ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5) [x86_64-linux]
`(?m)ruby (?P<version>[0-9]+\.[0-9]+\.[0-9]+((p|preview|rc|dev)[0-9]*)?) `) `(?m)ruby (?P<version>[0-9]+\.[0-9]+\.[0-9]+((p|preview|rc|dev)[0-9]*)?) `)
return []binutils.Classifier{ classifiers := []binutils.Classifier{
{ {
Class: "python-binary", Class: "python-binary",
FileGlob: "**/python*", FileGlob: "**/python*",
EvidenceMatcher: binutils.EvidenceMatchers( EvidenceMatcher: binutils.MatchAny(
// try to find version information from libpython shared libraries // try to find version information from libpython shared libraries
binutils.SharedLibraryLookup( binutils.SharedLibraryLookup(
`^libpython[0-9]+(?:\.[0-9]+)+[a-z]?\.so.*$`, `^libpython[0-9]+(?:\.[0-9]+)+[a-z]?\.so.*$`,
@ -98,7 +98,7 @@ func DefaultClassifiers() []binutils.Classifier {
{ {
Class: "redis-binary", Class: "redis-binary",
FileGlob: "**/redis-server", FileGlob: "**/redis-server",
EvidenceMatcher: binutils.EvidenceMatchers( EvidenceMatcher: binutils.MatchAny(
// matches most recent versions of redis (~v7), e.g. "7.0.14buildkitsandbox-1702957741000000000" // matches most recent versions of redis (~v7), e.g. "7.0.14buildkitsandbox-1702957741000000000"
m.FileContentsVersionMatcher(`[^\d](?P<version>\d+.\d+\.\d+)buildkitsandbox-\d+`), m.FileContentsVersionMatcher(`[^\d](?P<version>\d+.\d+\.\d+)buildkitsandbox-\d+`),
// matches against older versions of redis (~v3 - v6), e.g. "4.0.11841ce7054bd9-1542359302000000000" // matches against older versions of redis (~v3 - v6), e.g. "4.0.11841ce7054bd9-1542359302000000000"
@ -113,73 +113,10 @@ func DefaultClassifiers() []binutils.Classifier {
cpe.Must("cpe:2.3:a:redis:redis:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource), cpe.Must("cpe:2.3:a:redis:redis:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
}, },
}, },
{
Class: "java-binary-openjdk",
FileGlob: "**/java",
EvidenceMatcher: binutils.MatchExcluding(
binutils.EvidenceMatchers(
m.FileContentsVersionMatcher(
// [NUL]openjdk[NUL]java[NUL]0.0[NUL]11.0.17+8-LTS[NUL]
// [NUL]openjdk[NUL]java[NUL]1.8[NUL]1.8.0_352-b08[NUL]
`(?m)\x00openjdk\x00java\x00(?P<release>[0-9]+[.0-9]*)\x00(?P<version>[0-9]+[^\x00]+)\x00`),
m.FileContentsVersionMatcher(
// arm64 versions: [NUL]0.0[NUL][NUL][NUL][NUL][NUL]11.0.22+7[NUL][NUL][NUL][NUL][NUL][NUL][NUL]openjdk[NUL]java[NUL]
`(?m)\x00(?P<release>[0-9]+[.0-9]*)\x00+(?P<version>[0-9]+[^\x00]+)\x00+openjdk\x00java`),
),
// don't match graalvm
"-jvmci-",
),
Package: "java/jre",
PURL: mustPURL("pkg:generic/java/jre@version"),
// TODO the updates might need to be part of the CPE Attributes, like: 1.8.0:update152
CPEs: singleCPE("cpe:2.3:a:oracle:openjdk:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "java-binary-ibm",
FileGlob: "**/java",
EvidenceMatcher: m.FileContentsVersionMatcher(
// [NUL]java[NUL]1.8[NUL][NUL][NUL][NUL]1.8.0-foreman_2022_09_22_15_30-b00[NUL]
`(?m)\x00java\x00(?P<release>[0-9]+[.0-9]+)\x00{4}(?P<version>[0-9]+[-._a-zA-Z0-9]+)\x00`),
Package: "java/jre",
PURL: mustPURL("pkg:generic/java/jre@version"),
CPEs: singleCPE("cpe:2.3:a:ibm:java:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "java-binary-oracle",
FileGlob: "**/java",
EvidenceMatcher: binutils.MatchExcluding(
m.FileContentsVersionMatcher(
// [NUL]19.0.1+10-21[NUL]
`(?m)\x00(?P<version>[0-9]+[.0-9]+[+][-0-9]+)\x00`),
// don't match openjdk
`\x00openjdk\x00`,
),
Package: "java/jre",
PURL: mustPURL("pkg:generic/java/jre@version"),
CPEs: singleCPE("cpe:2.3:a:oracle:jre:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "java-binary-graalvm",
FileGlob: "**/java",
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\x00(?P<version>[0-9]+[.0-9]+[.0-9]+\+[0-9]+-jvmci-[0-9]+[.0-9]+-b[0-9]+)\x00`),
Package: "java/graalvm",
PURL: mustPURL("pkg:generic/java/graalvm@version"),
CPEs: singleCPE("cpe:2.3:a:oracle:graalvm:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "java-binary-jdk",
FileGlob: "**/jdb",
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+(\+[0-9]+)?([-._a-zA-Z0-9]+)?)\x00`),
Package: "java/jdk",
PURL: mustPURL("pkg:generic/java/jdk@version"),
CPEs: singleCPE("cpe:2.3:a:oracle:jdk:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{ {
Class: "nodejs-binary", Class: "nodejs-binary",
FileGlob: "**/node", FileGlob: "**/node",
EvidenceMatcher: binutils.EvidenceMatchers( EvidenceMatcher: binutils.MatchAny(
// [NUL]node v0.10.48[NUL] // [NUL]node v0.10.48[NUL]
// [NUL]v0.12.18[NUL] // [NUL]v0.12.18[NUL]
// [NUL]v4.9.1[NUL] // [NUL]v4.9.1[NUL]
@ -221,7 +158,7 @@ func DefaultClassifiers() []binutils.Classifier {
{ {
Class: "haproxy-binary", Class: "haproxy-binary",
FileGlob: "**/haproxy", FileGlob: "**/haproxy",
EvidenceMatcher: binutils.EvidenceMatchers( EvidenceMatcher: binutils.MatchAny(
m.FileContentsVersionMatcher(`(?m)version (?P<version>[0-9]+\.[0-9]+(\.|-dev|-rc)[0-9]+)(-[a-z0-9]{7})?, released 20`), m.FileContentsVersionMatcher(`(?m)version (?P<version>[0-9]+\.[0-9]+(\.|-dev|-rc)[0-9]+)(-[a-z0-9]{7})?, released 20`),
m.FileContentsVersionMatcher(`(?m)HA-Proxy version (?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)`), m.FileContentsVersionMatcher(`(?m)HA-Proxy version (?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)`),
m.FileContentsVersionMatcher(`(?m)(?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)-[0-9a-zA-Z]{7}.+HAProxy version`), m.FileContentsVersionMatcher(`(?m)(?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)-[0-9a-zA-Z]{7}.+HAProxy version`),
@ -303,7 +240,7 @@ func DefaultClassifiers() []binutils.Classifier {
{ {
Class: "mysql-binary", Class: "mysql-binary",
FileGlob: "**/mysql", FileGlob: "**/mysql",
EvidenceMatcher: binutils.EvidenceMatchers( EvidenceMatcher: binutils.MatchAny(
// shutdown[NUL]8.0.37[NUL][NUL][NUL][NUL][NUL]mysql_real_esc // shutdown[NUL]8.0.37[NUL][NUL][NUL][NUL][NUL]mysql_real_esc
m.FileContentsVersionMatcher(`\x00(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)\x00+mysql`), m.FileContentsVersionMatcher(`\x00(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)\x00+mysql`),
// /export/home/pb2/build/sb_0-26781090-1516292385.58/release/mysql-8.0.4-rc/mysys_ssl/my_default.cc // /export/home/pb2/build/sb_0-26781090-1516292385.58/release/mysql-8.0.4-rc/mysys_ssl/my_default.cc
@ -380,7 +317,7 @@ func DefaultClassifiers() []binutils.Classifier {
{ {
Class: "ruby-binary", Class: "ruby-binary",
FileGlob: "**/ruby", FileGlob: "**/ruby",
EvidenceMatcher: binutils.EvidenceMatchers( EvidenceMatcher: binutils.MatchAny(
rubyMatcher, rubyMatcher,
binutils.SharedLibraryLookup( binutils.SharedLibraryLookup(
// try to find version information from libruby shared libraries // try to find version information from libruby shared libraries
@ -394,7 +331,7 @@ func DefaultClassifiers() []binutils.Classifier {
{ {
Class: "erlang-binary", Class: "erlang-binary",
FileGlob: "**/erlexec", FileGlob: "**/erlexec",
EvidenceMatcher: binutils.EvidenceMatchers( EvidenceMatcher: binutils.MatchAny(
m.FileContentsVersionMatcher( m.FileContentsVersionMatcher(
// <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/ // <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/
`(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`, `(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
@ -411,7 +348,7 @@ func DefaultClassifiers() []binutils.Classifier {
{ {
Class: "erlang-alpine-binary", Class: "erlang-alpine-binary",
FileGlob: "**/beam.smp", FileGlob: "**/beam.smp",
EvidenceMatcher: binutils.EvidenceMatchers( EvidenceMatcher: binutils.MatchAny(
m.FileContentsVersionMatcher( m.FileContentsVersionMatcher(
// <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/ // <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/
`(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`, `(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
@ -432,7 +369,7 @@ func DefaultClassifiers() []binutils.Classifier {
{ {
Class: "erlang-library", Class: "erlang-library",
FileGlob: "**/liberts_internal.a", FileGlob: "**/liberts_internal.a",
EvidenceMatcher: binutils.EvidenceMatchers( EvidenceMatcher: binutils.MatchAny(
m.FileContentsVersionMatcher( m.FileContentsVersionMatcher(
// <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/ // <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/
`(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`, `(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
@ -681,6 +618,8 @@ func DefaultClassifiers() []binutils.Classifier {
CPEs: singleCPE("cpe:2.3:a:google:chrome:*:*:*:*:*:*:*:*"), CPEs: singleCPE("cpe:2.3:a:google:chrome:*:*:*:*:*:*:*:*"),
}, },
} }
return append(classifiers, defaultJavaClassifiers()...)
} }
// singleCPE returns a []cpe.CPE with Source: Generated based on the cpe string or panics if the // singleCPE returns a []cpe.CPE with Source: Generated based on the cpe string or panics if the

View File

@ -0,0 +1,196 @@
package binary
import (
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
)
//nolint:funlen
func defaultJavaClassifiers() []binutils.Classifier {
m := binutils.ContextualEvidenceMatchers{CatalogerName: catalogerName}
return []binutils.Classifier{
{
Class: "java-binary",
FileGlob: "**/java",
EvidenceMatcher: binutils.BranchingEvidenceMatcher([]binutils.Classifier{
{
Class: "java-binary-graalvm",
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\x00(?P<version>[0-9]+[.0-9]+[.0-9]+\+[0-9]+-jvmci-[0-9]+[.0-9]+-b[0-9]+)\x00`),
Package: "graalvm",
PURL: mustPURL("pkg:generic/oracle/graalvm@version"),
CPEs: singleCPE("cpe:2.3:a:oracle:graalvm:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "java-binary-openjdk-zulu",
EvidenceMatcher: binutils.MatchAll(
binutils.MatchPath(`**/*zulu*/**`),
binutils.MatchAny(
m.FileContentsVersionMatcher(
// [NUL]openjdk[NUL]java[NUL]0.0[NUL]11.0.17+8-LTS[NUL]
`(?m)\x00java\x00(?P<release>[0-9]+[.0-9]*)\x00(?P<version>[0-9]+[^\x00]+)\x00`),
m.FileContentsVersionMatcher(
// arm64 versions: [NUL]0.0[NUL][NUL][NUL][NUL][NUL]11.0.22+7[NUL][NUL][NUL][NUL][NUL][NUL][NUL]openjdk[NUL]java[NUL]
`(?m)\x00(?P<release>[0-9]+[.0-9]*)\x00+(?P<version>[0-9]+[^\x00]+)\x00+(openjdk|java)`),
),
),
Package: "zulu",
PURL: mustPURL("pkg:generic/azul/zulu@version"),
CPEs: singleCPE("cpe:2.3:a:azul:zulu:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "java-binary-openjdk-with-update",
EvidenceMatcher: binutils.MatchAny(
m.FileContentsVersionMatcher(
`openjdk`,
// [NUL]openjdk[NUL]java[NUL]1.8[NUL]1.8.0_352-b08[NUL]
`(?m)java\x00(?P<release>[0-9]+[.0-9]*)\x00(?P<version>(?<primary>[0-9]+[^\x00]+)_(?<update>[^\x00]+)-[^\x00]+)\x00`),
m.FileContentsVersionMatcher(
`openjdk`,
// arm64 versions: [NUL]0.0[NUL][NUL][NUL][NUL][NUL]1.8.0_352-b08[NUL][NUL][NUL][NUL][NUL][NUL][NUL]openjdk[NUL]java[NUL]
`(?m)\x00(?P<release>[0-9]+[.0-9]*)\x00+(?P<version>(?<primary>[0-9]+[^\x00]+)_(?<update>[^\x00]+)-[^\x00]+)\x00+openjdk\x00java`),
),
Package: "openjdk",
PURL: mustPURL("pkg:generic/oracle/openjdk@version"),
CPEs: singleCPE("cpe:2.3:a:oracle:openjdk:{{.primary}}:update{{.update}}:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "java-binary-openjdk",
EvidenceMatcher: binutils.MatchAny(
m.FileContentsVersionMatcher(
// [NUL]openjdk[NUL]java[NUL]0.0[NUL]11.0.17+8-LTS[NUL]
`(?m)\x00openjdk\x00java\x00(?P<release>[0-9]+[.0-9]*)\x00(?P<version>[0-9]+[^\x00]+)\x00`),
m.FileContentsVersionMatcher(
// arm64 versions: [NUL]0.0[NUL][NUL][NUL][NUL][NUL]11.0.22+7[NUL][NUL][NUL][NUL][NUL][NUL][NUL]openjdk[NUL]java[NUL]
`(?m)\x00(?P<release>[0-9]+[.0-9]*)\x00+(?P<version>[0-9]+[^\x00]+)\x00+openjdk\x00java`),
),
Package: "openjdk",
PURL: mustPURL("pkg:generic/oracle/openjdk@version"),
CPEs: singleCPE("cpe:2.3:a:oracle:openjdk:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "java-binary-ibm",
EvidenceMatcher: binutils.MatchAll(
binutils.MatchAny(
binutils.MatchPath("**/ibm/**"),
binutils.SharedLibraryLookup(
`^libjli\.so$`,
m.FileContentsVersionMatcher("IBM_JAVA")),
),
m.FileContentsVersionMatcher(
// [NUL]java[NUL]1.8[NUL][NUL][NUL]1.8.0-foreman_2022_01_20_09_33-b00[NUL]
`(?m)\x00java\x00+(?P<release>[0-9]+[.0-9]+)\x00+(?P<version>[0-9]+[-._a-zA-Z0-9]+)\x00`),
),
Package: "java",
PURL: mustPURL("pkg:generic/ibm/java@version"),
CPEs: singleCPE("cpe:2.3:a:ibm:java:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "java-binary-openjdk-fallthrough",
EvidenceMatcher: m.FileContentsVersionMatcher(
"openjdk",
// [NUL]19.0.1+10-21[NUL]
`(?m)\x00(?P<version>[0-9]+[.0-9]+[+][-0-9]+)\x00`,
),
Package: "jre",
PURL: mustPURL("pkg:generic/oracle/jre@version"),
CPEs: singleCPE("cpe:2.3:a:oracle:jre:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "java-binary-oracle",
EvidenceMatcher: m.FileContentsVersionMatcher(
// [NUL]19.0.1+10-21[NUL]
// java[NUL]1.8[NUL]1.8.0_451-b10
`(?m)\x00(?P<version>[0-9]+\.[0-9]+\.[-._+a-zA-Z0-9]+)\x00`,
),
Package: "jre",
PURL: mustPURL("pkg:generic/oracle/jre@version"),
CPEs: singleCPE("cpe:2.3:a:oracle:jre:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
}...),
},
{
Class: "java-jdb-binary",
FileGlob: "**/jdb",
EvidenceMatcher: binutils.BranchingEvidenceMatcher([]binutils.Classifier{
{
Class: "java-binary-graalvm",
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\x00(?P<version>[0-9]+[.0-9]+[.0-9]+\+[0-9]+-jvmci-[0-9]+[.0-9]+-b[0-9]+)\x00`),
Package: "graalvm",
PURL: mustPURL("pkg:generic/oracle/graalvm@version"),
CPEs: singleCPE("cpe:2.3:a:oracle:graalvm_for_jdk:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "java-jdb-binary-openjdk",
EvidenceMatcher: binutils.MatchAll(
m.FileContentsVersionMatcher(
// [NUL]openjdk[NUL]java[NUL]0.0[NUL]11.0.17+8-LTS[NUL]
// [NUL]openjdk[NUL]java[NUL]1.8[NUL]1.8.0_352-b08[NUL]
`(?m)\x00openjdk\x00java\x00(?P<release>[0-9]+[.0-9]*)\x00(?P<version>[0-9]+[^\x00]+)\x00`),
m.FileContentsVersionMatcher(
// arm64 versions: [NUL]0.0[NUL][NUL][NUL][NUL][NUL]11.0.22+7[NUL][NUL][NUL][NUL][NUL][NUL][NUL]openjdk[NUL]java[NUL]
`(?m)\x00(?P<release>[0-9]+[.0-9]*)\x00+(?P<version>[0-9]+[^\x00]+)\x00+openjdk\x00java`),
),
Package: "openjdk",
PURL: mustPURL("pkg:generic/oracle/openjdk@version"),
CPEs: singleCPE("cpe:2.3:a:oracle:openjdk:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "jdb-binary-openjdk-zulu",
EvidenceMatcher: binutils.MatchAll(
binutils.MatchPath("**/*zulu*/**"),
binutils.MatchAny(
m.FileContentsVersionMatcher(
// [NUL]openjdk[NUL]java[NUL]0.0[NUL]11.0.17+8-LTS[NUL]
`(?m)\x00openjdk\x00java\x00(?P<release>[0-9]+[.0-9]*)\x00(?P<version>[0-9]+[^\x00]+)\x00`),
m.FileContentsVersionMatcher(
// arm64 versions: [NUL]0.0[NUL][NUL][NUL][NUL][NUL]11.0.22+7[NUL][NUL][NUL][NUL][NUL][NUL][NUL]openjdk[NUL]java[NUL]
`(?m)\x00(?P<release>[0-9]+[.0-9]*)\x00+(?P<version>[0-9]+[^\x00]+)\x00+openjdk\x00java`),
),
),
Package: "zulu",
PURL: mustPURL("pkg:generic/azul/zulu@version"),
CPEs: singleCPE("cpe:2.3:a:azul:zulu:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "java-sdk-binary-ibm",
EvidenceMatcher: binutils.MatchAll(
binutils.MatchAny(
binutils.MatchPath("**/ibm/**"),
binutils.SharedLibraryLookup(
`^libjli\.so$`,
m.FileContentsVersionMatcher("IBM_JAVA")),
),
m.FileContentsVersionMatcher(
// [NUL]java[NUL]./lib/tools.jar./lib/sa-jdi.jar./classes.-J-ms8m[NUL][NUL]1.8[NUL][NUL][NUL]1.8.0-foreman_2022_01_20_09_33-b00[NUL]
`(?m)\x00java\x00.+?\x00(?P<version>[0-9]+[-._a-zA-Z0-9]+)\x00`),
),
Package: "java_sdk",
PURL: mustPURL("pkg:generic/ibm/java_sdk@version"),
CPEs: singleCPE("cpe:2.3:a:ibm:java_sdk:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "java-binary-openjdk-fallthrough",
EvidenceMatcher: binutils.MatchAll(
m.FileContentsVersionMatcher(
"openjdk",
`(?m)\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+(\+[0-9]+)?([-._a-zA-Z0-9]+)?)\x00`),
),
Package: "openjdk",
PURL: mustPURL("pkg:generic/oracle/openjdk@version"),
CPEs: singleCPE("cpe:2.3:a:oracle:openjdk:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "java-binary-jdk",
EvidenceMatcher: m.FileContentsVersionMatcher(
`(?m)\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+(\+[0-9]+)?([-._a-zA-Z0-9]+)?)\x00`),
Package: "jdk",
PURL: mustPURL("pkg:generic/oracle/jdk@version"),
CPEs: singleCPE("cpe:2.3:a:oracle:jdk:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
}...),
},
}
}

View File

@ -14,5 +14,5 @@ type EvidenceMatcher = binutils.EvidenceMatcher
func FileContentsVersionMatcher( func FileContentsVersionMatcher(
pattern string, pattern string,
) EvidenceMatcher { ) EvidenceMatcher {
return binutils.FileContentsVersionMatcher(pattern, catalogerName) return binutils.FileContentsVersionMatcher(catalogerName, pattern)
} }

View File

@ -194,6 +194,25 @@ from-images:
platform: linux/amd64 platform: linux/amd64
paths: paths:
- /opt/ibm/java/jre/bin/java - /opt/ibm/java/jre/bin/java
- /opt/ibm/java/jre/lib/amd64/jli/libjli.so
- name: java-ibm-8-jre
version: 1.8.0_451
images:
- ref: ibmjava:8-jre@sha256:3588cd1cc9b8646fe03b3b15210e69b1b520f1321f8518b69c0e7013d702fd23
platform: linux/amd64
paths:
- /opt/ibm/java/jre/bin/java
- /opt/ibm/java/jre/lib/amd64/jli/libjli.so
- name: java-ibm-8-sdk-alpine
version: 1.8.0_321
images:
- ref: ibmjava:8-sdk-alpine@sha256:4f8ad2029e78f7b91721745a77fc6011a7c0e09b9edeffb6b20b6ec34a6e63cd
platform: linux/amd64
paths:
- /opt/ibm/java/bin/jdb
- /opt/ibm/java/lib/amd64/jli/libjli.so
- version: 10.6.15 - version: 10.6.15
images: images:
@ -320,6 +339,23 @@ from-images:
paths: paths:
- /usr/local/bin/node - /usr/local/bin/node
- name: java-jdk-oracle
version: 1.8.0_451-b10
images:
- ref: container-registry.oracle.com/java/jdk:8u451-oraclelinux8@sha256:f9c980e52853bd0a8815ad908c9a4025ed68cb52f31f3b8cd0cd442ee367bfd1
platform: linux/amd64
paths:
- /usr/java/jdk-8/bin/java
- /usr/java/jdk-8/bin/jdb
- name: java-jre-oracle-sdk
version: 1.8.0_451-b10
images:
- ref: container-registry.oracle.com/java/serverjre:1.8.0_451@sha256:fe78fe3efd292dceb5685882ce1b13eb78492f383ccfd52a1b88a4f755b9d996
platform: linux/amd64
paths:
- /usr/java/jdk-8/bin/java
- name: java-jre-openjdk - name: java-jre-openjdk
version: 1.8.0_352-b08 version: 1.8.0_352-b08
images: images:

View File

@ -0,0 +1,43 @@
package binutils
import (
"io"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/internal/unionreader"
"github.com/anchore/syft/syft/pkg"
)
// BranchingEvidenceMatcher loads the contents of the reader into memory, assuming that all
// classifier matchers are going to be reading the same file, e.g. a "java" binary, continuing to
// try classifier EvidenceMatcher until the first set of packages is found
func BranchingEvidenceMatcher(classifiers ...Classifier) EvidenceMatcher {
return func(_ Classifier, context MatcherContext) ([]pkg.Package, error) {
// we are scanning the same contents multiple times, so read it into memory for a reader that can reset
rdr, err := getReader(context)
if err != nil {
return nil, err
}
defer internal.CloseAndLogError(rdr, context.Location.RealPath)
if err != nil {
return nil, err
}
for _, c := range classifiers {
pkgs, err := c.EvidenceMatcher(c, MatcherContext{
Resolver: context.Resolver,
Location: context.Location,
GetReader: func(_ MatcherContext) (unionreader.UnionReader, error) {
_, err := rdr.Seek(0, io.SeekStart)
if err != nil {
return nil, err
}
return rdr, nil
},
})
if len(pkgs) > 0 || err != nil {
return pkgs, err
}
}
return nil, nil
}
}

View File

@ -0,0 +1,95 @@
package binutils
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/internal/unionreader"
)
func Test_BranchingMatcher(t *testing.T) {
matchingTest := FileContentsVersionMatcher("", `my-verison:(?<version>\d+\.\d+)`)
notMatchingTest := MatchPath("**/not-version*")
tests := []struct {
name string
matcher EvidenceMatcher
expectedPackageNames []string
}{
{
name: "not matching",
matcher: BranchingEvidenceMatcher(
Classifier{
EvidenceMatcher: MatchAll(
notMatchingTest,
matchingTest,
),
Package: "a-pkg",
},
),
expectedPackageNames: nil,
},
{
name: "both match",
matcher: BranchingEvidenceMatcher(
Classifier{
EvidenceMatcher: matchingTest,
Package: "a-pkg",
},
Classifier{
EvidenceMatcher: matchingTest,
Package: "b-pkg",
},
),
expectedPackageNames: []string{"a-pkg"},
},
{
name: "first-does-not-match",
matcher: BranchingEvidenceMatcher(
Classifier{
EvidenceMatcher: MatchAll(
notMatchingTest,
matchingTest,
),
Package: "b-pkg",
},
Classifier{
EvidenceMatcher: matchingTest,
Package: "c-pkg",
},
),
expectedPackageNames: []string{"c-pkg"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
resolver := file.NewMockResolverForPaths("test-fixtures/version.txt", "test-fixtures/version-parts.txt")
locs, err := resolver.FilesByGlob("**/version.txt")
require.NoError(t, err)
require.Len(t, locs, 1)
loc := locs[0]
rdr, err := resolver.FileContentsByLocation(loc)
require.NoError(t, err)
urdr, err := unionreader.GetUnionReader(rdr)
require.NoError(t, err)
pkgs, err := test.matcher(Classifier{
Package: "a-pkg",
}, MatcherContext{
Resolver: resolver,
Location: loc,
GetReader: func(resolver MatcherContext) (unionreader.UnionReader, error) {
return urdr, nil
},
})
require.NoError(t, err)
var got []string
for i := range pkgs {
got = append(got, pkgs[i].Name)
}
require.EqualValues(t, test.expectedPackageNames, got)
})
}
}

View File

@ -8,11 +8,14 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"maps"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
"github.com/bmatcuk/doublestar/v4"
"github.com/anchore/packageurl-go" "github.com/anchore/packageurl-go"
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
@ -71,7 +74,8 @@ func (cfg Classifier) MarshalJSON() ([]byte, error) {
return json.Marshal(m) return json.Marshal(m)
} }
// EvidenceMatcher is a function called to catalog Packages that match some sort of evidence // EvidenceMatcher is a function called to identify based on some sort of evidence in the filesystem contents.
// A non-nil return value indicates a successful match, regardless of packages being returned.
type EvidenceMatcher func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) type EvidenceMatcher func(classifier Classifier, context MatcherContext) ([]pkg.Package, error)
type MatcherContext struct { type MatcherContext struct {
@ -80,13 +84,16 @@ type MatcherContext struct {
GetReader func(resolver MatcherContext) (unionreader.UnionReader, error) GetReader func(resolver MatcherContext) (unionreader.UnionReader, error)
} }
func EvidenceMatchers(matchers ...EvidenceMatcher) EvidenceMatcher { // MatchAny returns a combined evidence matcher that returns results from the first
// matcher that returns results
func MatchAny(matchers ...EvidenceMatcher) EvidenceMatcher {
return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) { return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) {
for _, matcher := range matchers { for _, matcher := range matchers {
match, err := matcher(classifier, context) match, err := matcher(classifier, context)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// only return when results
if match != nil { if match != nil {
return match, nil return match, nil
} }
@ -95,6 +102,23 @@ func EvidenceMatchers(matchers ...EvidenceMatcher) EvidenceMatcher {
} }
} }
// MatchAll executes all matchers until one returns nil results, only returning the final results
func MatchAll(matchers ...EvidenceMatcher) EvidenceMatcher {
return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) {
var out []pkg.Package
for _, matcher := range matchers {
match, err := matcher(classifier, context)
if match == nil || err != nil {
return nil, err
}
if len(match) > 0 {
out = match
}
}
return out, nil
}
}
type ContextualEvidenceMatchers struct { type ContextualEvidenceMatchers struct {
CatalogerName string CatalogerName string
} }
@ -103,8 +127,8 @@ func (c ContextualEvidenceMatchers) FileNameTemplateVersionMatcher(fileNamePatte
return FileNameTemplateVersionMatcher(fileNamePattern, contentTemplate, c.CatalogerName) return FileNameTemplateVersionMatcher(fileNamePattern, contentTemplate, c.CatalogerName)
} }
func (c ContextualEvidenceMatchers) FileContentsVersionMatcher(pattern string) EvidenceMatcher { func (c ContextualEvidenceMatchers) FileContentsVersionMatcher(patterns ...string) EvidenceMatcher {
return FileContentsVersionMatcher(pattern, c.CatalogerName) return FileContentsVersionMatcher(c.CatalogerName, patterns...)
} }
func FileNameTemplateVersionMatcher(fileNamePattern, contentTemplate, catalogerName string) EvidenceMatcher { func FileNameTemplateVersionMatcher(fileNamePattern, contentTemplate, catalogerName string) EvidenceMatcher {
@ -156,18 +180,37 @@ func FileNameTemplateVersionMatcher(fileNamePattern, contentTemplate, catalogerN
} }
} }
func FileContentsVersionMatcher(pattern, catalogerName string) EvidenceMatcher { // FileContentsVersionMatcher will match all provided patterns, extracting named capture groups from each pattern, overwriting earlier results
pat := regexp.MustCompile(pattern) func FileContentsVersionMatcher(catalogerName string, patterns ...string) EvidenceMatcher {
if len(patterns) == 0 {
panic("must specify at least one pattern")
}
var pats []*regexp.Regexp
for _, pattern := range patterns {
pats = append(pats, regexp.MustCompile(pattern))
}
return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) { return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) {
var matchMetadata map[string]string
for _, pat := range pats {
contents, err := getReader(context) contents, err := getReader(context)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to get read contents for file: %w", err) return nil, fmt.Errorf("unable to get read contents for file: %w", err)
} }
matchMetadata, err := internal.MatchNamedCaptureGroupsFromReader(pat, contents) match, err := internal.MatchNamedCaptureGroupsFromReader(pat, contents)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to match version: %w", err) return nil, fmt.Errorf("unable to match version: %w", err)
} }
if match == nil {
return nil, nil
}
if matchMetadata == nil {
matchMetadata = match
} else {
maps.Copy(matchMetadata, match)
}
}
// Convert {major: 1, minor: 2, patch: 3} to "1.2.3" // Convert {major: 1, minor: 2, patch: 3} to "1.2.3"
_, versionOk := matchMetadata["version"] _, versionOk := matchMetadata["version"]
@ -187,6 +230,10 @@ func FileContentsVersionMatcher(pattern, catalogerName string) EvidenceMatcher {
p := NewClassifierPackage(classifier, context.Location, matchMetadata, catalogerName) p := NewClassifierPackage(classifier, context.Location, matchMetadata, catalogerName)
if p == nil { if p == nil {
if matchMetadata != nil {
// if we had a successful metadata match, but no packages, return a successful match result
return []pkg.Package{}, nil
}
return nil, nil return nil, nil
} }
@ -194,29 +241,6 @@ func FileContentsVersionMatcher(pattern, catalogerName string) EvidenceMatcher {
} }
} }
// MatchExcluding tests the provided regular expressions against the file, and if matched, DOES NOT return
// anything that the matcher would otherwise return
func MatchExcluding(matcher EvidenceMatcher, contentPatternsToExclude ...string) EvidenceMatcher {
var nonMatchPatterns []*regexp.Regexp
for _, p := range contentPatternsToExclude {
nonMatchPatterns = append(nonMatchPatterns, regexp.MustCompile(p))
}
return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) {
contents, err := getReader(context)
if err != nil {
return nil, fmt.Errorf("unable to get read contents for file: %w", err)
}
matches, err := internal.MatchAnyFromReader(contents, nonMatchPatterns...)
if err != nil {
return nil, fmt.Errorf("unable to match content: %w", err)
}
if matches {
return nil, nil
}
return matcher(classifier, context)
}
}
func SharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher EvidenceMatcher) EvidenceMatcher { func SharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher EvidenceMatcher) EvidenceMatcher {
pat := regexp.MustCompile(sharedLibraryPattern) pat := regexp.MustCompile(sharedLibraryPattern)
return func(classifier Classifier, context MatcherContext) (packages []pkg.Package, _ error) { return func(classifier Classifier, context MatcherContext) (packages []pkg.Package, _ error) {
@ -234,16 +258,19 @@ func SharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher Evide
return nil, err return nil, err
} }
for _, libraryLocation := range locations { for _, libraryLocation := range locations {
// create a new resolver without the cached context lookup -- this is decidedly a different file
newResolver := MatcherContext{ newResolver := MatcherContext{
Resolver: context.Resolver, Resolver: context.Resolver,
Location: libraryLocation, Location: libraryLocation,
GetReader: context.GetReader,
} }
newResolver.Location = libraryLocation
pkgs, err := sharedLibraryMatcher(classifier, newResolver) pkgs, err := sharedLibraryMatcher(classifier, newResolver)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// not a successful match
if pkgs == nil {
continue
}
for _, p := range pkgs { for _, p := range pkgs {
// set the source binary as the first location // set the source binary as the first location
locationSet := file.NewLocationSet(context.Location) locationSet := file.NewLocationSet(context.Location)
@ -260,12 +287,28 @@ func SharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher Evide
} }
packages = append(packages, p) packages = append(packages, p)
} }
// return non-nil package results as a successful match indication if the evidence matcher returned a successful match indication
if packages == nil {
packages = pkgs
}
} }
} }
return packages, nil return packages, nil
} }
} }
func MatchPath(path string) EvidenceMatcher {
if !doublestar.ValidatePattern(path) {
panic("invalid pattern")
}
return func(_ Classifier, context MatcherContext) ([]pkg.Package, error) {
if doublestar.MatchUnvalidated(path, context.Location.RealPath) {
return []pkg.Package{}, nil // return non-nil
}
return nil, nil
}
}
func getReader(context MatcherContext) (unionreader.UnionReader, error) { func getReader(context MatcherContext) (unionreader.UnionReader, error) {
if context.GetReader != nil { if context.GetReader != nil {
return context.GetReader(context) return context.GetReader(context)

View File

@ -1,9 +1,12 @@
package binutils package binutils
import ( import (
"bytes"
"reflect" "reflect"
"text/template"
"github.com/anchore/packageurl-go" "github.com/anchore/packageurl-go"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -21,8 +24,8 @@ func NewClassifierPackage(classifier Classifier, location file.Location, matchMe
var cpes []cpe.CPE var cpes []cpe.CPE
for _, c := range classifier.CPEs { for _, c := range classifier.CPEs {
c.Attributes.Version = version c.Attributes.Version = templatedUpdate(c.Attributes.Version, matchMetadata, version)
c.Attributes.Update = update c.Attributes.Update = templatedUpdate(c.Attributes.Update, matchMetadata, update)
cpes = append(cpes, c) cpes = append(cpes, c)
} }
@ -55,3 +58,28 @@ func NewClassifierPackage(classifier Classifier, location file.Location, matchMe
return &p return &p
} }
func templatedUpdate(providedValue string, matchMetadata map[string]string, defaultValue string) string {
// if no template provided, just use the value directly
if providedValue == "" {
return defaultValue
}
// support templated updates
t, err := template.New("").Option("missingkey=zero").Parse(providedValue)
if err != nil {
log.Debugf("unable to parse classifier template=%q : %w", providedValue, err)
} else {
update := bytes.Buffer{}
err = t.Execute(&update, matchMetadata)
if err != nil {
log.Debugf("unable to render template: %w", err)
} else {
// only use the template result if it's non-empty
providedValue = update.String()
if providedValue != "" {
return providedValue
}
}
}
return defaultValue
}

View File

@ -27,7 +27,7 @@ func Test_ClassifierCPEs(t *testing.T) {
classifier: Classifier{ classifier: Classifier{
Package: "some-app", Package: "some-app",
FileGlob: "**/version.txt", FileGlob: "**/version.txt",
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`, "cataloger-name"), EvidenceMatcher: FileContentsVersionMatcher("cataloger-name", `(?m)my-verison:(?P<version>[0-9.]+)`),
CPEs: []cpe.CPE{}, CPEs: []cpe.CPE{},
}, },
cpes: nil, cpes: nil,
@ -38,7 +38,7 @@ func Test_ClassifierCPEs(t *testing.T) {
classifier: Classifier{ classifier: Classifier{
Package: "some-app", Package: "some-app",
FileGlob: "**/version.txt", FileGlob: "**/version.txt",
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`, "cataloger-name"), EvidenceMatcher: FileContentsVersionMatcher("cataloger-name", `(?m)my-verison:(?P<version>[0-9.]+)`),
CPEs: []cpe.CPE{ CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*", cpe.GeneratedSource), cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
}, },
@ -53,7 +53,7 @@ func Test_ClassifierCPEs(t *testing.T) {
classifier: Classifier{ classifier: Classifier{
Package: "some-app", Package: "some-app",
FileGlob: "**/version.txt", FileGlob: "**/version.txt",
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`, "cataloger-name"), EvidenceMatcher: FileContentsVersionMatcher("cataloger-name", `(?m)my-verison:(?P<version>[0-9.]+)`),
CPEs: []cpe.CPE{ CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*", cpe.GeneratedSource), cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
cpe.Must("cpe:2.3:a:some:apps:*:*:*:*:*:*:*:*", cpe.GeneratedSource), cpe.Must("cpe:2.3:a:some:apps:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
@ -70,7 +70,7 @@ func Test_ClassifierCPEs(t *testing.T) {
classifier: Classifier{ classifier: Classifier{
Package: "some-app", Package: "some-app",
FileGlob: "**/version-parts.txt", FileGlob: "**/version-parts.txt",
EvidenceMatcher: FileContentsVersionMatcher(`(?m)\x00(?P<major>[0-9.]+)\x00(?P<minor>[0-9.]+)\x00(?P<patch>[0-9.]+)\x00`, "cataloger-name"), EvidenceMatcher: FileContentsVersionMatcher("cataloger-name", `(?m)\x00(?P<major>[0-9.]+)\x00(?P<minor>[0-9.]+)\x00(?P<patch>[0-9.]+)\x00`),
CPEs: []cpe.CPE{}, CPEs: []cpe.CPE{},
}, },
cpes: nil, cpes: nil,
@ -113,7 +113,7 @@ func TestClassifier_MarshalJSON(t *testing.T) {
classifier: Classifier{ classifier: Classifier{
Class: "class", Class: "class",
FileGlob: "glob", FileGlob: "glob",
EvidenceMatcher: FileContentsVersionMatcher(".thing", "cataloger-name"), EvidenceMatcher: FileContentsVersionMatcher("cataloger-name", ".thing"),
Package: "pkg", Package: "pkg",
PURL: packageurl.PackageURL{ PURL: packageurl.PackageURL{
Type: "type", Type: "type",
@ -168,7 +168,7 @@ func TestFileContentsVersionMatcher(t *testing.T) {
mockGetContent := func(context MatcherContext) (unionreader.UnionReader, error) { mockGetContent := func(context MatcherContext) (unionreader.UnionReader, error) {
return unionreader.GetUnionReader(io.NopCloser(bytes.NewBufferString(tt.data))) return unionreader.GetUnionReader(io.NopCloser(bytes.NewBufferString(tt.data)))
} }
fn := FileContentsVersionMatcher(tt.pattern, "cataloger-name") fn := FileContentsVersionMatcher("cataloger-name", tt.pattern)
p, err := fn(Classifier{}, MatcherContext{ p, err := fn(Classifier{}, MatcherContext{
GetReader: mockGetContent, GetReader: mockGetContent,
}) })

View File

@ -11,7 +11,7 @@ import (
"path" "path"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/cpe"
@ -36,16 +36,16 @@ func TestParseNativeImage(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) { t.Run(test.fixture, func(t *testing.T) {
f, err := os.Open("test-fixtures/java-builds/packages/" + test.fixture) f, err := os.Open("test-fixtures/java-builds/packages/" + test.fixture)
assert.NoError(t, err) require.NoError(t, err)
readerCloser := io.NopCloser(f) readerCloser := io.NopCloser(f)
reader, err := unionreader.GetUnionReader(readerCloser) reader, err := unionreader.GetUnionReader(readerCloser)
assert.NoError(t, err) require.NoError(t, err)
parsed := false parsed := false
readers, err := unionreader.GetReaders(reader) readers, err := unionreader.GetReaders(reader)
assert.NoError(t, err) require.NoError(t, err)
for _, r := range readers { for _, r := range readers {
ni, err := test.newFn(test.fixture, r) ni, err := test.newFn(test.fixture, r)
assert.NoError(t, err) require.NoError(t, err)
_, _, err = ni.fetchPkgs() _, _, err = ni.fetchPkgs()
if err == nil { if err == nil {
t.Fatalf("should have failed to extract SBOM.") t.Fatalf("should have failed to extract SBOM.")
@ -228,11 +228,11 @@ func buildPackageMap(packages []pkg.Package) map[string]pkg.Package {
} }
func verifyPackageFields(t *testing.T, expected, actual pkg.Package) { func verifyPackageFields(t *testing.T, expected, actual pkg.Package) {
assert.Equal(t, expected.Name, actual.Name) require.Equal(t, expected.Name, actual.Name)
assert.Equal(t, expected.Version, actual.Version) require.Equal(t, expected.Version, actual.Version)
assert.Equal(t, expected.FoundBy, actual.FoundBy) require.Equal(t, expected.FoundBy, actual.FoundBy)
assert.Equal(t, expected.PURL, actual.PURL) require.Equal(t, expected.PURL, actual.PURL)
assert.ElementsMatch(t, expected.CPEs, actual.CPEs) require.ElementsMatch(t, expected.CPEs, actual.CPEs)
} }
func verifyRelationships(t *testing.T, expected, actual []artifact.Relationship) { func verifyRelationships(t *testing.T, expected, actual []artifact.Relationship) {
@ -260,8 +260,8 @@ func buildRelationshipMap(relationships []artifact.Relationship) map[string][]ar
} }
func verifyRelationshipFields(t *testing.T, expected, actual []artifact.Relationship) { func verifyRelationshipFields(t *testing.T, expected, actual []artifact.Relationship) {
assert.Equal(t, len(expected), len(actual)) require.Equal(t, len(expected), len(actual))
for i := range expected { for i := range expected {
assert.Equal(t, expected[i].Type, actual[i].Type) require.Equal(t, expected[i].Type, actual[i].Type)
} }
} }

View File

@ -91,7 +91,7 @@ func parseJVMRelease(_ context.Context, resolver file.Resolver, _ *generic.Envir
// the reason we use the reference to get the real path is in cases where the file cataloger is involved // the reason we use the reference to get the real path is in cases where the file cataloger is involved
// (thus the real path is not available except for in the original file reference). This is important // (thus the real path is not available except for in the original file reference). This is important
// since the path is critical for distinguishing between different JVM vendors. // since the path is critical for distinguishing between different JVM vendors.
vendor, product := jvmPrimaryVendorProduct(ri.Implementor, string(reader.Reference().RealPath), ri.ImageType, hasJdk) vendor, product := jvmPrimaryVendorProduct(ri, string(reader.Reference().RealPath), hasJdk)
p := pkg.Package{ p := pkg.Package{
Name: product, Name: product,
@ -163,11 +163,11 @@ func jvmPurl(ri pkg.JavaVMRelease, version, vendor, product string) string {
return pURL.ToString() return pURL.ToString()
} }
func jvmPrimaryVendorProduct(implementor, path, imageType string, hasJdk bool) (string, string) { func jvmPrimaryVendorProduct(ri *pkg.JavaVMRelease, path string, hasJdk bool) (string, string) {
implementor = strings.ReplaceAll(strings.ToLower(implementor), " ", "") implementor := strings.ReplaceAll(strings.ToLower(ri.Implementor), " ", "")
pickProduct := func() string { pickProduct := func() string {
if hasJdk || jvmProjectByType(imageType) == jdk { if hasJdk || jvmProjectByType(ri.ImageType) == jdk {
return jdk return jdk
} }
return jre return jre
@ -180,7 +180,13 @@ func jvmPrimaryVendorProduct(implementor, path, imageType string, hasJdk bool) (
case strings.Contains(implementor, "sun"): case strings.Contains(implementor, "sun"):
return "sun", pickProduct() return "sun", pickProduct()
case strings.Contains(implementor, "oracle") || strings.Contains(path, "oracle"): case strings.Contains(implementor, "ibm") || strings.Contains(path, "/ibm"):
if hasJdk {
return "ibm", "java_sdk"
}
return "ibm", "java"
case strings.Contains(implementor, "oracle") || strings.Contains(path, "oracle") || strings.Contains(ri.BuildType, "commercial"):
return oracleVendor, pickProduct() return oracleVendor, pickProduct()
} }
return oracleVendor, openJdkProduct return oracleVendor, openJdkProduct

View File

@ -307,6 +307,7 @@ func TestJvmPrimaryVendorProduct(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
implementor string implementor string
buildType string
path string path string
imageType string imageType string
hasJdk bool hasJdk bool
@ -340,6 +341,25 @@ func TestJvmPrimaryVendorProduct(t *testing.T) {
expectedVendor: "oracle", expectedVendor: "oracle",
expectedProduct: "jre", expectedProduct: "jre",
}, },
{
name: "Oracle commercial build type with JRE",
implementor: "",
buildType: "commercial",
path: "/usr/lib/jvm/jdk8/release",
imageType: "JRE",
hasJdk: false,
expectedVendor: "oracle",
expectedProduct: "jre",
},
{
name: "Oracle commercial build type with JDK",
implementor: "",
buildType: "commercial",
path: "/usr/lib/jvm/jdk8/release",
hasJdk: true,
expectedVendor: "oracle",
expectedProduct: "jdk",
},
{ {
name: "Oracle vendor with JDK in path", name: "Oracle vendor with JDK in path",
implementor: "", implementor: "",
@ -367,11 +387,29 @@ func TestJvmPrimaryVendorProduct(t *testing.T) {
expectedVendor: "oracle", // corretto upstream is oracle openjdk expectedVendor: "oracle", // corretto upstream is oracle openjdk
expectedProduct: "openjdk", expectedProduct: "openjdk",
}, },
{
name: "IBM JRE",
path: "/opt/ibm/java/release",
expectedVendor: "ibm",
expectedProduct: "java",
},
{
name: "IBM JDK",
path: "/opt/ibm/java/release",
hasJdk: true,
expectedVendor: "ibm",
expectedProduct: "java_sdk",
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
vendor, product := jvmPrimaryVendorProduct(tt.implementor, tt.path, tt.imageType, tt.hasJdk) ri := pkg.JavaVMRelease{
Implementor: tt.implementor,
BuildType: tt.buildType,
ImageType: tt.imageType,
}
vendor, product := jvmPrimaryVendorProduct(&ri, tt.path, tt.hasJdk)
assert.Equal(t, tt.expectedVendor, vendor) assert.Equal(t, tt.expectedVendor, vendor)
assert.Equal(t, tt.expectedProduct, product) assert.Equal(t, tt.expectedProduct, product)
}) })

View File

@ -219,7 +219,7 @@ func (p interpreterCataloger) getClassifier(realPath string) (string, *binutils.
return name, &binutils.Classifier{ return name, &binutils.Classifier{
Class: fmt.Sprintf("php-ext-%s-binary", name), Class: fmt.Sprintf("php-ext-%s-binary", name),
EvidenceMatcher: binutils.FileContentsVersionMatcher(match, p.name), EvidenceMatcher: binutils.FileContentsVersionMatcher(p.name, match),
Package: name, Package: name,
PURL: packageurl.PackageURL{ PURL: packageurl.PackageURL{
Type: packageurl.TypeGeneric, Type: packageurl.TypeGeneric,