Add support for GraalVM Native Image executables. (#1276)

Signed-off-by: William Blair <william.blair@oracle.com>
This commit is contained in:
William Blair 2023-01-06 18:31:22 -05:00 committed by GitHub
parent db386baf81
commit e480443c8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 760 additions and 24 deletions

7
CONTRIBUTORS.md Normal file
View File

@ -0,0 +1,7 @@
# Syft Contributors
The following Syft components were contributed by external authors/organizations.
## GraalVM Native Image
A cataloger contributed by Oracle Corporation that extracts packages given within GraalVM Native Image SBOMs.

View File

@ -40,7 +40,7 @@ For commercial support options with Syft or Grype, please [contact Anchore](http
- Objective-C (cocoapods) - Objective-C (cocoapods)
- Go (go.mod, Go binaries) - Go (go.mod, Go binaries)
- Haskell (cabal, stack) - Haskell (cabal, stack)
- Java (jar, ear, war, par, sar) - Java (jar, ear, war, par, sar, native-image)
- JavaScript (npm, yarn) - JavaScript (npm, yarn)
- Jenkins Plugins (jpi, hpi) - Jenkins Plugins (jpi, hpi)
- PHP (composer) - PHP (composer)

View File

@ -44,6 +44,7 @@ func ImageCatalogers(cfg Config) []pkg.Cataloger {
deb.NewDpkgdbCataloger(), deb.NewDpkgdbCataloger(),
rpm.NewRpmDBCataloger(), rpm.NewRpmDBCataloger(),
java.NewJavaCataloger(cfg.Java()), java.NewJavaCataloger(cfg.Java()),
java.NewNativeImageCataloger(),
apkdb.NewApkdbCataloger(), apkdb.NewApkdbCataloger(),
golang.NewGoModuleBinaryCataloger(), golang.NewGoModuleBinaryCataloger(),
dotnet.NewDotnetDepsCataloger(), dotnet.NewDotnetDepsCataloger(),
@ -67,6 +68,7 @@ func DirectoryCatalogers(cfg Config) []pkg.Cataloger {
rpm.NewFileCataloger(), rpm.NewFileCataloger(),
java.NewJavaCataloger(cfg.Java()), java.NewJavaCataloger(cfg.Java()),
java.NewJavaPomCataloger(), java.NewJavaPomCataloger(),
java.NewNativeImageCataloger(),
apkdb.NewApkdbCataloger(), apkdb.NewApkdbCataloger(),
golang.NewGoModuleBinaryCataloger(), golang.NewGoModuleBinaryCataloger(),
golang.NewGoModFileCataloger(), golang.NewGoModFileCataloger(),
@ -97,6 +99,7 @@ func AllCatalogers(cfg Config) []pkg.Cataloger {
rpm.NewFileCataloger(), rpm.NewFileCataloger(),
java.NewJavaCataloger(cfg.Java()), java.NewJavaCataloger(cfg.Java()),
java.NewJavaPomCataloger(), java.NewJavaPomCataloger(),
java.NewNativeImageCataloger(),
apkdb.NewApkdbCataloger(), apkdb.NewApkdbCataloger(),
golang.NewGoModuleBinaryCataloger(), golang.NewGoModuleBinaryCataloger(),
golang.NewGoModFileCataloger(), golang.NewGoModFileCataloger(),

View File

@ -1,5 +1,5 @@
/* /*
Package java provides a concrete Cataloger implementation for Java archives (jar, war, ear, par, sar, jpi, hpi formats). Package java provides a concrete Cataloger implementation for Java archives (jar, war, ear, par, sar, jpi, hpi, and native-image formats).
*/ */
package java package java

View File

@ -0,0 +1,550 @@
package java
import (
"bytes"
"compress/gzip"
"debug/elf"
"debug/macho"
"debug/pe"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io"
"unsafe"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader"
"github.com/anchore/syft/syft/source"
)
type nativeImageCycloneDX struct {
BomFormat string `json:"bomFormat"`
SpecVersion string `json:"specVersion"`
Version int `json:"version"`
Components []nativeImageComponent `json:"components"`
}
type nativeImageComponent struct {
Type string `json:"type"`
Group string `json:"group"`
Name string `json:"name"`
Version string `json:"version"`
Properties []nativeImageCPE `json:"properties"`
}
type nativeImageCPE struct {
Name string `json:"name"`
Value string `json:"value"`
}
type nativeImage interface {
fetchPkgs() ([]pkg.Package, error)
}
type nativeImageElf struct {
file *elf.File
}
type nativeImageMachO struct {
file *macho.File
}
type exportTypesPE struct {
functionPointer uint32
namePointer uint32
headerAttribute uint32
}
type exportPrefixPE struct {
characteristics uint32
timeDateStamp uint32
majorVersion uint16
minorVersion uint16
name uint32
base uint32
}
type exportContentPE struct {
// Directory Entry Contents for finding SBOM symbols
numberOfFunctions uint32
numberOfNames uint32
addressOfFunctions uint32
addressOfNames uint32
// Locations of SBOM symbols in the .data section
addressOfSbom uint32
addressOfSbomLength uint32
addressOfSvmVersion uint32
}
// A nativeImagePE must maintain the underlying reader to fetch information unavailable in the Golang API.
type nativeImagePE struct {
file *pe.File
reader io.ReaderAt
exportSymbols pe.DataDirectory
exports []byte
t exportTypesPE
header exportPrefixPE
}
type NativeImageCataloger struct{}
const nativeImageCatalogerName = "graalvm-native-image-cataloger"
const nativeImageSbomSymbol = "sbom"
const nativeImageSbomLengthSymbol = "sbom_length"
const nativeImageSbomVersionSymbol = "__svm_version_info"
const nativeImageMissingSymbolsError = "one or more symbols are missing from the native image executable"
const nativeImageInvalidIndexError = "parsing the executable file generated an invalid index"
const nativeImageMissingExportedDataDirectoryError = "exported data directory is missing"
// newNativeImageCataloger returns a new Native Image cataloger object.
func NewNativeImageCataloger() *NativeImageCataloger {
return &NativeImageCataloger{}
}
// Name returns a string that uniquely describes a native image cataloger
func (c *NativeImageCataloger) Name() string {
return nativeImageCatalogerName
}
// getPackage returns the package given within a NativeImageComponent.
func getPackage(component nativeImageComponent) pkg.Package {
var cpes []cpe.CPE
for _, property := range component.Properties {
cpe, err := cpe.New(property.Value)
if err != nil {
log.Debugf("native-image cataloger: could not parse CPE: %v.", err)
continue
}
cpes = append(cpes, cpe)
}
return pkg.Package{
Name: component.Name,
Version: component.Version,
Language: pkg.Java,
Type: pkg.GraalVMNativeImagePkg,
MetadataType: pkg.JavaMetadataType,
FoundBy: nativeImageCatalogerName,
Metadata: pkg.JavaMetadata{
PomProperties: &pkg.PomProperties{
GroupID: component.Group,
},
},
CPEs: cpes,
}
}
// decompressSbom returns the packages given within a native image executable's SBOM.
func decompressSbom(databuf []byte, sbomStart uint64, lengthStart uint64) ([]pkg.Package, error) {
var pkgs []pkg.Package
lengthEnd := lengthStart + 8
buflen := len(databuf)
if lengthEnd > uint64(buflen) {
return nil, errors.New("the sbom_length symbol overflows the binary")
}
length := databuf[lengthStart:lengthEnd]
p := bytes.NewBuffer(length)
var storedLength uint64
err := binary.Read(p, binary.LittleEndian, &storedLength)
if err != nil {
log.Debugf("native-image-cataloger: could not read from binary file.")
return nil, err
}
log.Tracef("native-image cataloger: found SBOM of length %d.", storedLength)
sbomEnd := sbomStart + storedLength
if sbomEnd > uint64(buflen) {
return nil, errors.New("the sbom symbol overflows the binary")
}
sbomCompressed := databuf[sbomStart:sbomEnd]
p = bytes.NewBuffer(sbomCompressed)
gzreader, err := gzip.NewReader(p)
if err != nil {
log.Debugf("native-image cataloger: could not decompress the SBOM.")
return nil, err
}
output, err := io.ReadAll(gzreader)
if err != nil {
log.Debugf("native-image cataloger: could not read the decompressed SBOM.")
return nil, err
}
var sbomContent nativeImageCycloneDX
err = json.Unmarshal(output, &sbomContent)
if err != nil {
log.Debugf("native-image cataloger: could not unmarshal JSON.")
return nil, err
}
for _, component := range sbomContent.Components {
p := getPackage(component)
pkgs = append(pkgs, p)
}
return pkgs, nil
}
// fileError logs an error message when an executable cannot be read.
func fileError(filename string, err error) (nativeImage, error) {
// We could not read the file as a binary for the desired platform, but it may still be a native-image executable.
log.Debugf("native-image cataloger: unable to read executable (file=%q): %v.", filename, err)
return nil, err
}
// newElf reads a Native Image from an ELF executable.
func newElf(filename string, r io.ReaderAt) (nativeImage, error) {
// First attempt to read an ELF file.
bi, err := elf.NewFile(r)
// The reader does not refer to an ELF file.
if err != nil {
return fileError(filename, err)
}
return nativeImageElf{
file: bi,
}, nil
}
// newMachO reads a Native Image from a Mach O executable.
func newMachO(filename string, r io.ReaderAt) (nativeImage, error) {
// First attempt to read an ELF file.
bi, err := macho.NewFile(r)
// The reader does not refer to an MachO file.
if err != nil {
return fileError(filename, err)
}
return nativeImageMachO{
file: bi,
}, nil
}
// newPE reads a Native Image from a Portable Executable file.
func newPE(filename string, r io.ReaderAt) (nativeImage, error) {
// First attempt to read an ELF file.
bi, err := pe.NewFile(r)
// The reader does not refer to an MachO file.
if err != nil {
return fileError(filename, err)
}
optionalHeader := bi.OptionalHeader.(*pe.OptionalHeader64)
exportSymbolsDataDirectory := optionalHeader.DataDirectory[0]
// If we have no exported symbols it is not a Native Image
if exportSymbolsDataDirectory.Size == 0 {
return fileError(filename, errors.New(nativeImageMissingExportedDataDirectoryError))
}
exportSymbolsOffset := uint64(exportSymbolsDataDirectory.VirtualAddress)
exports := make([]byte, exportSymbolsDataDirectory.Size)
_, err = r.ReadAt(exports, int64(exportSymbolsOffset))
if err != nil {
log.Debugf("native-image cataloger: could not read the exported symbols data directory: %v.", err)
return fileError(filename, err)
}
return nativeImagePE{
file: bi,
reader: r,
exportSymbols: exportSymbolsDataDirectory,
exports: exports,
t: exportTypesPE{
functionPointer: 0,
namePointer: 0,
headerAttribute: 0,
},
header: exportPrefixPE{
characteristics: 0,
timeDateStamp: 0,
majorVersion: 0,
minorVersion: 0,
name: 0,
base: 0,
},
}, nil
}
// fetchPkgs obtains the packages given in the binary.
func (ni nativeImageElf) fetchPkgs() ([]pkg.Package, error) {
bi := ni.file
var sbom elf.Symbol
var sbomLength elf.Symbol
var svmVersion elf.Symbol
si, err := bi.Symbols()
if err != nil {
log.Debugf("native-image cataloger: no symbols found.")
return nil, err
}
for _, s := range si {
switch s.Name {
case nativeImageSbomSymbol:
sbom = s
case nativeImageSbomLengthSymbol:
sbomLength = s
case nativeImageSbomVersionSymbol:
svmVersion = s
}
}
if sbom.Value == 0 || sbomLength.Value == 0 || svmVersion.Value == 0 {
log.Debugf("native-image cataloger: %v", nativeImageMissingSymbolsError)
return nil, errors.New(nativeImageMissingSymbolsError)
}
dataSection := bi.Section(".data")
if dataSection == nil {
log.Debugf("native-image cataloger: .data section missing from ELF file.")
return nil, err
}
dataSectionBase := dataSection.SectionHeader.Addr
data, err := dataSection.Data()
if err != nil {
log.Debugf("native-image cataloger: cannot read the .data section.")
return nil, err
}
sbomLocation := sbom.Value - dataSectionBase
lengthLocation := sbomLength.Value - dataSectionBase
return decompressSbom(data, sbomLocation, lengthLocation)
}
// fetchPkgs obtains the packages from a Native Image given as a Mach O file.
func (ni nativeImageMachO) fetchPkgs() ([]pkg.Package, error) {
var sbom macho.Symbol
var sbomLength macho.Symbol
var svmVersion macho.Symbol
bi := ni.file
for _, s := range bi.Symtab.Syms {
switch s.Name {
case "_" + nativeImageSbomSymbol:
sbom = s
case "_" + nativeImageSbomLengthSymbol:
sbomLength = s
case "_" + nativeImageSbomVersionSymbol:
svmVersion = s
}
}
if sbom.Value == 0 || sbomLength.Value == 0 || svmVersion.Value == 0 {
log.Debugf("native-image cataloger: %v.", nativeImageMissingSymbolsError)
return nil, errors.New(nativeImageMissingSymbolsError)
}
dataSegment := bi.Segment("__DATA")
if dataSegment == nil {
return nil, nil
}
databuf, err := dataSegment.Data()
if err != nil {
log.Debugf("native-image cataloger: cannot obtain buffer from data segment.")
return nil, nil
}
sbomLocation := sbom.Value - dataSegment.Addr
lengthLocation := sbomLength.Value - dataSegment.Addr
return decompressSbom(databuf, sbomLocation, lengthLocation)
}
// fetchExportAttribute obtains an attribute from the exported symbols directory entry.
func (ni nativeImagePE) fetchExportAttribute(i int) (uint32, error) {
var attribute uint32
n := len(ni.exports)
j := int(unsafe.Sizeof(ni.header)) + i*int(unsafe.Sizeof(ni.t.headerAttribute))
if j+4 >= n {
log.Debugf("native-image cataloger: invalid index to export directory entry attribute: %v.", j)
return uint32(0), errors.New(nativeImageInvalidIndexError)
}
p := bytes.NewBuffer(ni.exports[j : j+4])
err := binary.Read(p, binary.LittleEndian, &attribute)
if err != nil {
log.Debugf("native-image cataloger: error fetching export directory entry attribute: %v.", err)
return uint32(0), err
}
return attribute, nil
}
// fetchExportFunctionPointer obtains a function pointer from the exported symbols directory entry.
func (ni nativeImagePE) fetchExportFunctionPointer(functionsBase uint32, i uint32) (uint32, error) {
var pointer uint32
n := uint32(len(ni.exports))
sz := uint32(unsafe.Sizeof(ni.t.functionPointer))
j := functionsBase + i*sz
if j+sz >= n {
log.Debugf("native-image cataloger: invalid index to exported function: %v.", j)
return uint32(0), errors.New(nativeImageInvalidIndexError)
}
p := bytes.NewBuffer(ni.exports[j : j+sz])
err := binary.Read(p, binary.LittleEndian, &pointer)
if err != nil {
log.Debugf("native-image cataloger: error fetching exported function: %v.", err)
return uint32(0), err
}
return pointer, nil
}
// fetchExportContent obtains the content of the export directory entry relevant to the SBOM.
func (ni nativeImagePE) fetchExportContent() (*exportContentPE, error) {
content := new(exportContentPE)
var err error
content.numberOfFunctions, err = ni.fetchExportAttribute(0)
if err != nil {
log.Debugf("native-image cataloger: could not find the number of exported functions attribute: %v", err)
return nil, err
}
content.numberOfNames, err = ni.fetchExportAttribute(1)
if err != nil {
log.Debugf("native-image cataloger: could not find the number of exported names attribute: %v", err)
return nil, err
}
content.addressOfFunctions, err = ni.fetchExportAttribute(2)
if err != nil {
log.Debugf("native-image cataloger: could not find the exported functions attribute: %v", err)
return nil, err
}
content.addressOfNames, err = ni.fetchExportAttribute(3)
if err != nil {
log.Debugf("native-image cataloger: could not find the exported names attribute: %v", err)
return nil, err
}
return content, nil
}
// fetchSbomSymbols enumerates the symbols exported by a binary to detect Native Image's SBOM symbols.
func (ni nativeImagePE) fetchSbomSymbols(content *exportContentPE) {
// Appending NULL bytes to symbol names simplifies finding them in the export data directory
sbomBytes := []byte(nativeImageSbomSymbol + "\x00")
sbomLengthBytes := []byte(nativeImageSbomLengthSymbol + "\x00")
svmVersionInfoBytes := []byte(nativeImageSbomVersionSymbol + "\x00")
n := uint32(len(ni.exports))
// Find SBOM, SBOM Length, and SVM Version Symbol
for i := uint32(0); i < content.numberOfNames; i++ {
j := i * uint32(unsafe.Sizeof(ni.t.namePointer))
addressBase := content.addressOfNames - ni.exportSymbols.VirtualAddress
k := addressBase + j
sz := uint32(unsafe.Sizeof(ni.t.namePointer))
if k+sz >= n {
log.Debugf("native-image cataloger: invalid index to exported function: %v.", k)
// If we are at the end of exports, stop looking
return
}
var symbolAddress uint32
p := bytes.NewBuffer(ni.exports[k : k+sz])
err := binary.Read(p, binary.LittleEndian, &symbolAddress)
if err != nil {
log.Debugf("native-image cataloger: error fetching address of symbol %v.", err)
return
}
symbolBase := symbolAddress - ni.exportSymbols.VirtualAddress
if symbolBase >= n {
log.Debugf("native-image cataloger: invalid index to exported symbol: %v.", symbolBase)
return
}
switch {
case bytes.HasPrefix(ni.exports[symbolBase:], sbomBytes):
content.addressOfSbom = i
case bytes.HasPrefix(ni.exports[symbolBase:], sbomLengthBytes):
content.addressOfSbomLength = i
case bytes.HasPrefix(ni.exports[symbolBase:], svmVersionInfoBytes):
content.addressOfSvmVersion = i
}
}
}
// fetchPkgs obtains the packages from a Native Image given as a PE file.
func (ni nativeImagePE) fetchPkgs() ([]pkg.Package, error) {
content, err := ni.fetchExportContent()
if err != nil {
log.Debugf("native-image cataloger: could not fetch the content of the export directory entry: %v.", err)
return nil, err
}
ni.fetchSbomSymbols(content)
if content.addressOfSbom == uint32(0) || content.addressOfSbomLength == uint32(0) || content.addressOfSvmVersion == uint32(0) {
log.Debugf("native-image cataloger: %v.", nativeImageMissingSymbolsError)
return nil, errors.New(nativeImageMissingSymbolsError)
}
functionsBase := content.addressOfFunctions - ni.exportSymbols.VirtualAddress
sbomOffset := content.addressOfSbom
sbomAddress, err := ni.fetchExportFunctionPointer(functionsBase, sbomOffset)
if err != nil {
log.Debugf("native-image cataloger: cannot fetch SBOM pointer from exported functions: %v.", err)
return nil, err
}
sbomLengthOffset := content.addressOfSbomLength
sbomLengthAddress, err := ni.fetchExportFunctionPointer(functionsBase, sbomLengthOffset)
if err != nil {
log.Debugf("native-image cataloger: cannot fetch SBOM length pointer from exported functions: %v.", err)
return nil, err
}
bi := ni.file
dataSection := bi.Section(".data")
if dataSection == nil {
return nil, nil
}
databuf, err := dataSection.Data()
if err != nil {
log.Debugf("native-image cataloger: cannot obtain buffer from .data section.")
return nil, nil
}
sbomLocation := sbomAddress - dataSection.VirtualAddress
lengthLocation := sbomLengthAddress - dataSection.VirtualAddress
return decompressSbom(databuf, uint64(sbomLocation), uint64(lengthLocation))
}
// fetchPkgs provides the packages available in a UnionReader.
func fetchPkgs(reader unionreader.UnionReader, filename string) []pkg.Package {
var pkgs []pkg.Package
imageformats := []func(string, io.ReaderAt) (nativeImage, error){newElf, newMachO, newPE}
// NOTE: multiple readers are returned to cover universal binaries, which are files
// with more than one binary
readers, err := unionreader.GetReaders(reader)
if err != nil {
log.Debugf("native-image cataloger: failed to open a binary: %v.", err)
return nil
}
for _, r := range readers {
for _, makeNativeImage := range imageformats {
ni, err := makeNativeImage(filename, r)
if err != nil {
continue
}
newpkgs, err := ni.fetchPkgs()
if err != nil {
log.Debugf("native-image cataloger: error extracting SBOM from %s: %v.", filename, err)
continue
}
pkgs = append(pkgs, newpkgs...)
}
}
return pkgs
}
// Catalog attempts to find any native image executables reachable from a resolver.
func (c *NativeImageCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
var pkgs []pkg.Package
fileMatches, err := resolver.FilesByMIMEType(internal.ExecutableMIMETypeSet.List()...)
if err != nil {
return pkgs, nil, fmt.Errorf("failed to find binaries by mime types: %w", err)
}
for _, location := range fileMatches {
readerCloser, err := resolver.FileContentsByLocation(location)
if err != nil {
log.Debugf("native-image cataloger: error opening file: %v.", err)
continue
}
log.Tracef("native-image cataloger: found an executable file %v.", location)
reader, err := unionreader.GetUnionReader(readerCloser)
if err != nil {
return nil, nil, err
}
newpkgs := fetchPkgs(reader, location.RealPath)
pkgs = append(pkgs, newpkgs...)
internal.CloseAndLogError(readerCloser, location.RealPath)
}
return pkgs, nil, nil
}

View File

@ -0,0 +1,123 @@
package java
import (
"bufio"
"bytes"
"compress/gzip"
"encoding/binary"
"io"
"io/ioutil"
"os"
"path"
"testing"
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader"
)
func TestParseNativeImage(t *testing.T) {
tests := []struct {
fixture string
}{
{
fixture: "test-fixtures/java-builds/packages/example-java-app",
},
}
for _, test := range tests {
t.Run(path.Base(test.fixture), func(t *testing.T) {
f, err := os.Open(test.fixture)
assert.NoError(t, err)
readerCloser := io.ReadCloser(ioutil.NopCloser(f))
reader, err := unionreader.GetUnionReader(readerCloser)
assert.NoError(t, err)
parsed := false
readers, err := unionreader.GetReaders(reader)
for _, r := range readers {
ni, err := newElf(test.fixture, r)
assert.NoError(t, err)
_, err = ni.fetchPkgs()
if err == nil {
t.Fatalf("should have failed to extract SBOM.")
}
// If we can enumerate the symbols in the binary, we can parse the binary.
if err.Error() == nativeImageMissingSymbolsError {
parsed = true
}
}
if !parsed {
t.Fatalf("Could not parse the Native Image executable: %v", test.fixture)
}
})
}
}
func TestParseNativeImageSbom(t *testing.T) {
tests := []struct {
fixture string
expected []pkg.Package
}{
{
fixture: "test-fixtures/graalvm-sbom/micronaut.json",
expected: []pkg.Package{
{
Name: "netty-codec-http2",
Version: "4.1.73.Final",
Language: pkg.Java,
Type: pkg.GraalVMNativeImagePkg,
MetadataType: pkg.JavaMetadataType,
FoundBy: nativeImageCatalogerName,
Metadata: pkg.JavaMetadata{
PomProperties: &pkg.PomProperties{
GroupID: "io.netty",
},
},
CPEs: []cpe.CPE{
{
Part: "a",
Vendor: "codec",
Product: "codec",
Version: "4.1.73.Final",
},
{
Part: "a",
Vendor: "codec",
Product: "netty-codec-http2",
Version: "4.1.73.Final",
},
{
Part: "a",
Vendor: "codec",
Product: "netty_codec_http2",
Version: "4.1.73.Final",
},
},
},
},
},
}
for _, test := range tests {
t.Run(path.Base(test.fixture), func(t *testing.T) {
// Create a buffer to resemble a compressed SBOM in a native image.
sbom, err := ioutil.ReadFile(test.fixture)
assert.NoError(t, err)
var b bytes.Buffer
writebytes := bufio.NewWriter(&b)
z := gzip.NewWriter(writebytes)
_, err = z.Write(sbom)
assert.NoError(t, err)
z.Close()
writebytes.Flush()
compressedsbom := b.Bytes()
sbomlength := uint64(len(compressedsbom))
binary.Write(writebytes, binary.LittleEndian, sbomlength)
writebytes.Flush()
compressedsbom = b.Bytes()
actual, err := decompressSbom(compressedsbom, 0, sbomlength)
assert.NoError(t, err)
assert.Equal(t, test.expected, actual)
})
}
}

View File

@ -0,0 +1,28 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"version": 1,
"components": [
{
"type": "library",
"group": "io.netty",
"name": "netty-codec-http2",
"version": "4.1.73.Final",
"properties": [
{
"name": "syft:cpe23",
"value": "cpe:2.3:a:codec:codec:4.1.73.Final:*:*:*:*:*:*:*"
},
{
"name": "syft:cpe23",
"value": "cpe:2.3:a:codec:netty-codec-http2:4.1.73.Final:*:*:*:*:*:*:*"
},
{
"name": "syft:cpe23",
"value": "cpe:2.3:a:codec:netty_codec_http2:4.1.73.Final:*:*:*:*:*:*:*"
}
]
}
],
"serialNumber": "urn:uuid:43538af4-f715-3d85-9629-336fdd3790ad"
}

View File

@ -4,7 +4,7 @@ ifndef PKGSDIR
$(error PKGSDIR is not set) $(error PKGSDIR is not set)
endif endif
all: jars archives all: jars archives native-image
clean: clean-examples clean: clean-examples
rm -f $(PKGSDIR)/* rm -f $(PKGSDIR)/*
@ -17,6 +17,8 @@ jars: $(PKGSDIR)/example-java-app-maven-0.1.0.jar $(PKGSDIR)/example-java-app-gr
archives: $(PKGSDIR)/example-java-app-maven-0.1.0.zip $(PKGSDIR)/example-java-app-maven-0.1.0.tar $(PKGSDIR)/example-java-app-maven-0.1.0.tar.gz archives: $(PKGSDIR)/example-java-app-maven-0.1.0.zip $(PKGSDIR)/example-java-app-maven-0.1.0.tar $(PKGSDIR)/example-java-app-maven-0.1.0.tar.gz
native-image: $(PKGSDIR)/example-java-app
# jars within archives... # jars within archives...
$(PKGSDIR)/example-java-app-maven-0.1.0.zip: $(PKGSDIR)/example-java-app-maven-0.1.0.jar $(PKGSDIR)/example-java-app-maven-0.1.0.zip: $(PKGSDIR)/example-java-app-maven-0.1.0.jar
@ -62,6 +64,10 @@ clean-jenkins:
example-jenkins-plugin/dependency-reduced-pom.xml \ example-jenkins-plugin/dependency-reduced-pom.xml \
example-jenkins-plugin/*.exploding example-jenkins-plugin/*.exploding
# Native Image...
$(PKGSDIR)/example-java-app: $(PKGSDIR)/example-java-app-maven-0.1.0.jar
./build-example-java-app-native-image.sh $(PKGSDIR)
# we need a way to determine if CI should bust the test cache based on the source material # we need a way to determine if CI should bust the test cache based on the source material
$(PKGSDIR).fingerprint: clean-examples $(PKGSDIR).fingerprint: clean-examples
mkdir -p $(PKGSDIR) mkdir -p $(PKGSDIR)

View File

@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -uxe
PKGSDIR=$1
CTRID=$(docker create -v /example-java-app ghcr.io/graalvm/native-image:22.2.0 -cp /example-java-app/example-java-app-maven-0.1.0.jar --no-fallback -H:Class=hello.HelloWorld -H:Name=example-java-app)
function cleanup() {
docker rm "${CTRID}"
}
trap cleanup EXIT
set +e
docker cp "${PKGSDIR}/example-java-app-maven-0.1.0.jar" "${CTRID}:/example-java-app/"
docker start -a "${CTRID}"
docker cp "${CTRID}:/app/example-java-app" $PKGSDIR

View File

@ -20,6 +20,7 @@ const (
PythonPkg Type = "python" PythonPkg Type = "python"
PhpComposerPkg Type = "php-composer" PhpComposerPkg Type = "php-composer"
JavaPkg Type = "java-archive" JavaPkg Type = "java-archive"
GraalVMNativeImagePkg Type = "graalvm-native-image"
JenkinsPluginPkg Type = "jenkins-plugin" JenkinsPluginPkg Type = "jenkins-plugin"
GoModulePkg Type = "go-module" GoModulePkg Type = "go-module"
RustPkg Type = "rust-crate" RustPkg Type = "rust-crate"