mirror of
https://github.com/anchore/syft.git
synced 2026-07-04 18:18:26 +02:00
add file-based toolchain detection
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
58e4dbbf01
commit
1c72d03da3
@ -17,8 +17,6 @@ import (
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/cataloging/filecataloging"
|
||||
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
|
||||
"github.com/anchore/syft/syft/file/cataloger/executable"
|
||||
"github.com/anchore/syft/syft/file/cataloger/filecontent"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/binary"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/dotnet"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||
@ -144,18 +142,14 @@ func (cfg Catalog) ToFilesConfig() filecataloging.Config {
|
||||
log.WithFields("error", err).Warn("unable to configure file hashers")
|
||||
}
|
||||
|
||||
return filecataloging.Config{
|
||||
Selection: cfg.File.Metadata.Selection,
|
||||
Hashers: hashers,
|
||||
Content: filecontent.Config{
|
||||
Globs: cfg.File.Content.Globs,
|
||||
SkipFilesAboveSize: cfg.File.Content.SkipFilesAboveSize,
|
||||
},
|
||||
Executable: executable.Config{
|
||||
MIMETypes: executable.DefaultConfig().MIMETypes,
|
||||
Globs: cfg.File.Executable.Globs,
|
||||
},
|
||||
}
|
||||
c := filecataloging.DefaultConfig()
|
||||
c.Selection = cfg.File.Metadata.Selection
|
||||
c.Hashers = hashers
|
||||
c.Content.Globs = cfg.File.Content.Globs
|
||||
c.Content.SkipFilesAboveSize = cfg.File.Content.SkipFilesAboveSize
|
||||
c.Executable.Globs = cfg.File.Executable.Globs
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (cfg Catalog) ToLicenseConfig() cataloging.LicenseConfig {
|
||||
|
||||
@ -64,7 +64,7 @@ func (c *fileConfig) PostLoad() error {
|
||||
}
|
||||
|
||||
func (c *fileConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||
descriptions.Add(&c.Metadata.Selection, `select which files should be captured by the file-metadata cataloger and included in the SBOM.
|
||||
descriptions.Add(&c.Metadata.Selection, `select which files should be captured by the file-metadata cataloger and included in the SBOM.
|
||||
Options include:
|
||||
- "all": capture all files from the search space
|
||||
- "owned-by-package": capture only files owned by packages
|
||||
|
||||
@ -3,7 +3,7 @@ package internal
|
||||
const (
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "16.1.4"
|
||||
JSONSchemaVersion = "16.1.5"
|
||||
|
||||
// Changelog
|
||||
// 16.1.0 - reformulated the python pdm fields (added "URL" and removed the unused "path" field).
|
||||
@ -11,5 +11,6 @@ const (
|
||||
// 16.1.2 - placeholder for 16.1.2 changelog
|
||||
// 16.1.3 - add GGUFFileParts to GGUFFileHeader metadata
|
||||
// 16.1.4 - add BunLockEntry metadata type for bun.lock support
|
||||
// 16.1.5 - add file executable toolchain information
|
||||
|
||||
)
|
||||
|
||||
4334
schema/json/schema-16.1.5.json
Normal file
4334
schema/json/schema-16.1.5.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "anchore.io/schema/syft/json/16.1.4/document",
|
||||
"$id": "anchore.io/schema/syft/json/16.1.5/document",
|
||||
"$ref": "#/$defs/Document",
|
||||
"$defs": {
|
||||
"AlpmDbEntry": {
|
||||
@ -1291,6 +1291,13 @@
|
||||
"elfSecurityFeatures": {
|
||||
"$ref": "#/$defs/ELFSecurityFeatures",
|
||||
"description": "ELFSecurityFeatures contains ELF-specific security hardening information when Format is ELF."
|
||||
},
|
||||
"toolchains": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Toolchain"
|
||||
},
|
||||
"type": "array",
|
||||
"description": "Toolchains captures information about the compiler, linker, runtime, or other toolchains used to build (or otherwise exist within) the executable."
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
@ -4269,6 +4276,27 @@
|
||||
],
|
||||
"description": "TerraformLockProviderEntry represents a single provider entry in a Terraform dependency lock file (.terraform.lock.hcl)."
|
||||
},
|
||||
"Toolchain": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name is the name of the toolchain (e.g., \"gcc\", \"clang\", \"rust\", \"lld\", \"GNU AS\", etc.)."
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "Version is the version of the toolchain."
|
||||
},
|
||||
"component": {
|
||||
"type": "string",
|
||||
"description": "Component indicates which part of the toolchain this represents (e.g., compiler, linker, assembler)."
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"component"
|
||||
]
|
||||
},
|
||||
"WordpressPluginEntry": {
|
||||
"properties": {
|
||||
"pluginInstallDirectory": {
|
||||
|
||||
@ -25,8 +25,11 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// MIMETypes are the MIME types that will be considered for executable cataloging.
|
||||
MIMETypes []string `json:"mime-types" yaml:"mime-types" mapstructure:"mime-types"`
|
||||
Globs []string `json:"globs" yaml:"globs" mapstructure:"globs"`
|
||||
|
||||
// Globs are the glob patterns that will be used to filter which files are cataloged.
|
||||
Globs []string `json:"globs" yaml:"globs" mapstructure:"globs"`
|
||||
}
|
||||
|
||||
type Cataloger struct {
|
||||
@ -106,6 +109,51 @@ func processExecutableLocation(loc file.Location, resolver file.Resolver) (*file
|
||||
return processExecutable(loc, uReader)
|
||||
}
|
||||
|
||||
func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file.Executable, error) {
|
||||
data := file.Executable{}
|
||||
|
||||
// determine the executable format
|
||||
|
||||
format, err := findExecutableFormat(reader)
|
||||
if err != nil {
|
||||
log.Debugf("unable to determine executable kind for %v: %v", loc.RealPath, err)
|
||||
return nil, fmt.Errorf("unable to determine executable kind: %w", err)
|
||||
}
|
||||
|
||||
if format == "" {
|
||||
// this is not an "unknown", so just log -- this binary does not have parseable data in it
|
||||
log.Debugf("unable to determine executable format for %q", loc.RealPath)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
data.Format = format
|
||||
|
||||
switch format {
|
||||
case file.ELF:
|
||||
if err = findELFFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err, "path", loc.RealPath).Trace("unable to determine ELF features")
|
||||
err = fmt.Errorf("unable to determine ELF features: %w", err)
|
||||
}
|
||||
case file.PE:
|
||||
if err = findPEFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err, "path", loc.RealPath).Trace("unable to determine PE features")
|
||||
err = fmt.Errorf("unable to determine PE features: %w", err)
|
||||
}
|
||||
case file.MachO:
|
||||
if err = findMachoFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err, "path", loc.RealPath).Trace("unable to determine Macho features")
|
||||
err = fmt.Errorf("unable to determine Macho features: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// always allocate collections for presentation
|
||||
if data.ImportedLibraries == nil {
|
||||
data.ImportedLibraries = []string{}
|
||||
}
|
||||
|
||||
return &data, err
|
||||
}
|
||||
|
||||
func catalogingProgress(locations int64) *monitor.TaskProgress {
|
||||
info := monitor.GenericTask{
|
||||
Title: monitor.Title{
|
||||
@ -152,51 +200,6 @@ func locationMatchesGlob(loc file.Location, globs []string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file.Executable, error) {
|
||||
data := file.Executable{}
|
||||
|
||||
// determine the executable format
|
||||
|
||||
format, err := findExecutableFormat(reader)
|
||||
if err != nil {
|
||||
log.Debugf("unable to determine executable kind for %v: %v", loc.RealPath, err)
|
||||
return nil, fmt.Errorf("unable to determine executable kind: %w", err)
|
||||
}
|
||||
|
||||
if format == "" {
|
||||
// this is not an "unknown", so just log -- this binary does not have parseable data in it
|
||||
log.Debugf("unable to determine executable format for %q", loc.RealPath)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
data.Format = format
|
||||
|
||||
switch format {
|
||||
case file.ELF:
|
||||
if err = findELFFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err, "path", loc.RealPath).Trace("unable to determine ELF features")
|
||||
err = fmt.Errorf("unable to determine ELF features: %w", err)
|
||||
}
|
||||
case file.PE:
|
||||
if err = findPEFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err, "path", loc.RealPath).Trace("unable to determine PE features")
|
||||
err = fmt.Errorf("unable to determine PE features: %w", err)
|
||||
}
|
||||
case file.MachO:
|
||||
if err = findMachoFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err, "path", loc.RealPath).Trace("unable to determine Macho features")
|
||||
err = fmt.Errorf("unable to determine Macho features: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// always allocate collections for presentation
|
||||
if data.ImportedLibraries == nil {
|
||||
data.ImportedLibraries = []string{}
|
||||
}
|
||||
|
||||
return &data, err
|
||||
}
|
||||
|
||||
func findExecutableFormat(reader unionreader.UnionReader) (file.ExecutableFormat, error) {
|
||||
// read the first sector of the file
|
||||
buf := make([]byte, 512)
|
||||
|
||||
@ -34,10 +34,33 @@ func findELFFeatures(data *file.Executable, reader unionreader.UnionReader) erro
|
||||
data.ELFSecurityFeatures = findELFSecurityFeatures(f)
|
||||
data.HasEntrypoint = elfHasEntrypoint(f)
|
||||
data.HasExports = elfHasExports(f)
|
||||
data.Toolchains = elfToolchains(reader, f)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func elfToolchains(reader unionreader.UnionReader, f *elf.File) []file.Toolchain {
|
||||
// parse the .comment section and symbol tables once and share them across the detectors
|
||||
comments := elfComments(f)
|
||||
symbols := elfSymbolSet(f)
|
||||
return includeNoneNil(
|
||||
golangToolchainEvidence(reader),
|
||||
cToolchainEvidence(comments, symbols),
|
||||
rustToolchainEvidence(comments),
|
||||
linkerToolchainEvidence(f, comments),
|
||||
)
|
||||
}
|
||||
|
||||
func includeNoneNil(evidence ...*file.Toolchain) []file.Toolchain {
|
||||
var toolchains []file.Toolchain
|
||||
for _, e := range evidence {
|
||||
if e != nil {
|
||||
toolchains = append(toolchains, *e)
|
||||
}
|
||||
}
|
||||
return toolchains
|
||||
}
|
||||
|
||||
func findELFSecurityFeatures(f *elf.File) *file.ELFSecurityFeatures {
|
||||
return &file.ELFSecurityFeatures{
|
||||
SymbolTableStripped: isElfSymbolTableStripped(f),
|
||||
@ -81,6 +104,28 @@ func boolRef(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
// elfSymbolSet collects the names from the static and dynamic symbol tables into a single set, so
|
||||
// detectors can test for runtime markers without re-reading and re-iterating the symbol tables each
|
||||
// time. It covers both tables since some evidence (e.g. the Fortran MAIN__ entry) lives only in the
|
||||
// static symbol table.
|
||||
func elfSymbolSet(f *elf.File) *strset.Set {
|
||||
set := strset.New()
|
||||
|
||||
if syms, err := f.Symbols(); err == nil {
|
||||
for _, sym := range syms {
|
||||
set.Add(sym.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if dynSyms, err := f.DynamicSymbols(); err == nil {
|
||||
for _, sym := range dynSyms {
|
||||
set.Add(sym.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func checkElfNXProtection(file *elf.File) bool {
|
||||
// find the program headers until you find the GNU_STACK segment
|
||||
for _, prog := range file.Progs {
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@ -226,3 +227,73 @@ func Test_elfHasExports(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_elfGoToolchainDetection(t *testing.T) {
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("testdata/golang", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
wantPresent bool
|
||||
}{
|
||||
{
|
||||
name: "go binary has toolchain",
|
||||
fixture: "bin/hello_linux",
|
||||
wantPresent: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
reader := readerForFixture(t, tt.fixture)
|
||||
f, err := elf.NewFile(reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
toolchains := elfToolchains(reader, f)
|
||||
assert.Equal(t, tt.wantPresent, hasGoToolchain(toolchains))
|
||||
|
||||
if tt.wantPresent {
|
||||
require.NotEmpty(t, toolchains)
|
||||
assert.Equal(t, "go", toolchains[0].Name)
|
||||
assert.NotEmpty(t, toolchains[0].Version)
|
||||
assert.Equal(t, file.ToolchainComponentCompiler, toolchains[0].Component)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_elfCgoToolchainDetection(t *testing.T) {
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("testdata/golang", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
t.Run("cgo binary has both go and c toolchains", func(t *testing.T) {
|
||||
reader := readerForFixture(t, "bin/hello_linux_cgo")
|
||||
f, err := elf.NewFile(reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
toolchains := elfToolchains(reader, f)
|
||||
|
||||
// versions are dynamic based on Docker image, so we ignore them in comparison
|
||||
want := []file.Toolchain{
|
||||
{Name: "go", Component: file.ToolchainComponentCompiler},
|
||||
{Name: "gcc", Component: file.ToolchainComponentCompiler},
|
||||
}
|
||||
|
||||
if d := cmp.Diff(want, toolchains, cmpopts.IgnoreFields(file.Toolchain{}, "Version")); d != "" {
|
||||
t.Errorf("elfToolchains() mismatch (-want +got):\n%s", d)
|
||||
}
|
||||
|
||||
// verify versions are populated
|
||||
for _, tc := range toolchains {
|
||||
assert.NotEmpty(t, tc.Version, "expected version to be set for %s toolchain", tc.Name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -48,6 +48,8 @@ func findMachoFeatures(data *file.Executable, reader unionreader.UnionReader) er
|
||||
if !data.HasExports {
|
||||
data.HasExports = machoHasExports(f)
|
||||
}
|
||||
|
||||
data.Toolchains = machoToolchains(reader)
|
||||
}
|
||||
|
||||
// de-duplicate libraries
|
||||
@ -56,6 +58,12 @@ func findMachoFeatures(data *file.Executable, reader unionreader.UnionReader) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func machoToolchains(reader unionreader.UnionReader) []file.Toolchain {
|
||||
return includeNoneNil(
|
||||
golangToolchainEvidence(reader),
|
||||
)
|
||||
}
|
||||
|
||||
func machoHasEntrypoint(f *macho.File) bool {
|
||||
// derived from struct entry_point_command found from which explicitly calls out LC_MAIN:
|
||||
// https://opensource.apple.com/source/xnu/xnu-2050.18.24/EXTERNAL_HEADERS/mach-o/loader.h
|
||||
|
||||
@ -120,3 +120,39 @@ func Test_machoUniversal(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_machoGoToolchainDetection(t *testing.T) {
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("testdata/golang", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
wantPresent bool
|
||||
}{
|
||||
{
|
||||
name: "go binary has toolchain",
|
||||
fixture: "bin/hello_mac",
|
||||
wantPresent: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
reader := readerForFixture(t, tt.fixture)
|
||||
|
||||
toolchains := machoToolchains(reader)
|
||||
assert.Equal(t, tt.wantPresent, hasGoToolchain(toolchains))
|
||||
|
||||
if tt.wantPresent {
|
||||
require.NotEmpty(t, toolchains)
|
||||
assert.Equal(t, "go", toolchains[0].Name)
|
||||
assert.NotEmpty(t, toolchains[0].Version)
|
||||
assert.Equal(t, file.ToolchainComponentCompiler, toolchains[0].Component)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ func findPEFeatures(data *file.Executable, reader unionreader.UnionReader) error
|
||||
data.ImportedLibraries = libs
|
||||
data.HasEntrypoint = peHasEntrypoint(f)
|
||||
data.HasExports = peHasExports(f)
|
||||
data.Toolchains = peToolchains(reader)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -82,3 +83,9 @@ func peHasExports(f *pe.File) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func peToolchains(reader unionreader.UnionReader) []file.Toolchain {
|
||||
return includeNoneNil(
|
||||
golangToolchainEvidence(reader),
|
||||
)
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
|
||||
@ -78,3 +79,39 @@ func Test_peHasExports(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_peGoToolchainDetection(t *testing.T) {
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("testdata/golang", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
wantPresent bool
|
||||
}{
|
||||
{
|
||||
name: "go binary has toolchain",
|
||||
fixture: "bin/hello.exe",
|
||||
wantPresent: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
reader := readerForFixture(t, tt.fixture)
|
||||
|
||||
toolchains := peToolchains(reader)
|
||||
assert.Equal(t, tt.wantPresent, hasGoToolchain(toolchains))
|
||||
|
||||
if tt.wantPresent {
|
||||
require.NotEmpty(t, toolchains)
|
||||
assert.Equal(t, "go", toolchains[0].Name)
|
||||
assert.NotEmpty(t, toolchains[0].Version)
|
||||
assert.Equal(t, file.ToolchainComponentCompiler, toolchains[0].Component)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
bin
|
||||
actual_verify
|
||||
Dockerfile.sha256
|
||||
Dockerfile.sha256
|
||||
*.fingerprint
|
||||
18
syft/file/cataloger/executable/testdata/golang/Dockerfile
vendored
Normal file
18
syft/file/cataloger/executable/testdata/golang/Dockerfile
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
FROM golang:1.24
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY main.go cgo_main.go ./
|
||||
|
||||
# pure Go builds (no CGO)
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /hello_linux .
|
||||
|
||||
RUN CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o /hello_mac .
|
||||
|
||||
RUN CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o /hello.exe .
|
||||
|
||||
# CGO-enabled build (Linux only, uses gcc)
|
||||
RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o /hello_linux_cgo ./cgo_main.go
|
||||
42
syft/file/cataloger/executable/testdata/golang/Makefile
vendored
Normal file
42
syft/file/cataloger/executable/testdata/golang/Makefile
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-golang-build-tools:latest
|
||||
FINGERPRINT_FILE=$(BIN).fingerprint
|
||||
|
||||
ifndef BIN
|
||||
$(error BIN is not set)
|
||||
endif
|
||||
|
||||
.DEFAULT_GOAL := fixtures
|
||||
|
||||
# requirement 1: 'fixtures' goal to generate any and all test fixtures
|
||||
fixtures: build
|
||||
|
||||
# requirement 2: 'fingerprint' goal to determine if the fixture input that indicates any existing cache should be busted
|
||||
fingerprint: $(FINGERPRINT_FILE)
|
||||
|
||||
tools-check:
|
||||
@sha256sum -c Dockerfile.sha256 || (echo "Tools Dockerfile has changed" && exit 1)
|
||||
|
||||
tools:
|
||||
@(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || \
|
||||
(docker build --platform=linux/amd64 -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
|
||||
|
||||
build: tools
|
||||
@mkdir -p $(BIN)
|
||||
docker run -i -v $(shell pwd)/$(BIN):/out $(TOOL_IMAGE) sh -c \
|
||||
"cp /hello_linux /hello_mac /hello.exe /hello_linux_cgo /out/"
|
||||
|
||||
debug:
|
||||
docker run -it --rm -v $(shell pwd):/mount -w /mount $(TOOL_IMAGE) sh
|
||||
|
||||
# requirement 3: we always need to recalculate the fingerprint based on source regardless of any existing fingerprint
|
||||
.PHONY: $(FINGERPRINT_FILE)
|
||||
$(FINGERPRINT_FILE):
|
||||
@find . -maxdepth 1 -type f \( -name "*.go" -o -name "go.*" -o -name "Dockerfile" -o -name "Makefile" \) \
|
||||
-exec sha256sum {} \; | sort -k2 > $(FINGERPRINT_FILE)
|
||||
|
||||
# requirement 4: 'clean' goal to remove all generated test fixtures
|
||||
clean:
|
||||
rm -rf $(BIN) Dockerfile.sha256 $(FINGERPRINT_FILE)
|
||||
|
||||
.PHONY: tools tools-check build debug clean fixtures fingerprint
|
||||
18
syft/file/cataloger/executable/testdata/golang/cgo_main.go
vendored
Normal file
18
syft/file/cataloger/executable/testdata/golang/cgo_main.go
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int get_length(const char* s) {
|
||||
return strlen(s);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
msg := C.CString("Hello from CGO!")
|
||||
length := C.get_length(msg)
|
||||
fmt.Printf("String length: %d\n", int(length))
|
||||
}
|
||||
8
syft/file/cataloger/executable/testdata/golang/go.mod
vendored
Normal file
8
syft/file/cataloger/executable/testdata/golang/go.mod
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
module x
|
||||
|
||||
go 1.24.4
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
golang.org/x/text v0.21.0
|
||||
)
|
||||
4
syft/file/cataloger/executable/testdata/golang/go.sum
vendored
Normal file
4
syft/file/cataloger/executable/testdata/golang/go.sum
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
28
syft/file/cataloger/executable/testdata/golang/main.go
vendored
Normal file
28
syft/file/cataloger/executable/testdata/golang/main.go
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// use stdlib packages
|
||||
fmt.Println("Hello from Go!")
|
||||
fmt.Println(strings.ToUpper("test"))
|
||||
|
||||
// use golang.org/x package
|
||||
tag := language.English
|
||||
fmt.Println(tag.String())
|
||||
|
||||
// use third-party package
|
||||
spew.Dump(os.Args)
|
||||
|
||||
// use encoding/json
|
||||
data, _ := json.Marshal(map[string]string{"hello": "world"})
|
||||
fmt.Println(string(data))
|
||||
}
|
||||
3
syft/file/cataloger/executable/testdata/toolchains/.gitignore
vendored
Normal file
3
syft/file/cataloger/executable/testdata/toolchains/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
bin/
|
||||
Dockerfile.sha256
|
||||
*.fingerprint
|
||||
30
syft/file/cataloger/executable/testdata/toolchains/Makefile
vendored
Normal file
30
syft/file/cataloger/executable/testdata/toolchains/Makefile
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
# invoke all make files in subdirectories
|
||||
.PHONY: all gcc clang lld mold gold rust fortran
|
||||
|
||||
all: gcc clang lld mold gold rust fortran
|
||||
|
||||
gcc:
|
||||
$(MAKE) -C gcc
|
||||
|
||||
clang:
|
||||
$(MAKE) -C clang
|
||||
|
||||
lld:
|
||||
$(MAKE) -C lld
|
||||
|
||||
mold:
|
||||
$(MAKE) -C mold
|
||||
|
||||
gold:
|
||||
$(MAKE) -C gold
|
||||
|
||||
rust:
|
||||
$(MAKE) -C rust
|
||||
|
||||
fortran:
|
||||
$(MAKE) -C fortran
|
||||
|
||||
%:
|
||||
@for dir in gcc clang lld mold gold rust fortran; do \
|
||||
$(MAKE) -C $$dir $@; \
|
||||
done
|
||||
6
syft/file/cataloger/executable/testdata/toolchains/clang/Dockerfile
vendored
Normal file
6
syft/file/cataloger/executable/testdata/toolchains/clang/Dockerfile
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
FROM alpine:3.21
|
||||
|
||||
RUN apk add --no-cache clang18 musl-dev make
|
||||
|
||||
# create symlink so 'clang' command works (Alpine installs as clang-18)
|
||||
RUN ln -s /usr/bin/clang-18 /usr/bin/clang
|
||||
39
syft/file/cataloger/executable/testdata/toolchains/clang/Makefile
vendored
Normal file
39
syft/file/cataloger/executable/testdata/toolchains/clang/Makefile
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-toolchain-clang-build-tools:latest
|
||||
FINGERPRINT_FILE=$(BIN).fingerprint
|
||||
|
||||
ifndef BIN
|
||||
$(error BIN is not set)
|
||||
endif
|
||||
|
||||
.DEFAULT_GOAL := fixtures
|
||||
|
||||
# requirement 1: 'fixtures' goal to generate any and all test fixtures
|
||||
fixtures: build
|
||||
|
||||
# requirement 2: 'fingerprint' goal to determine if the fixture input that indicates any existing cache should be busted
|
||||
fingerprint: $(FINGERPRINT_FILE)
|
||||
|
||||
tools-check:
|
||||
@sha256sum -c Dockerfile.sha256 || (echo "Tools Dockerfile has changed" && exit 1)
|
||||
|
||||
tools:
|
||||
@(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || (docker build --platform linux/amd64 -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
|
||||
|
||||
build: tools
|
||||
@mkdir -p $(BIN)
|
||||
docker run --platform linux/amd64 -i -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) make
|
||||
|
||||
debug:
|
||||
docker run --platform linux/amd64 -i --rm -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) bash
|
||||
|
||||
# requirement 3: we always need to recalculate the fingerprint based on source regardless of any existing fingerprint
|
||||
.PHONY: $(FINGERPRINT_FILE)
|
||||
$(FINGERPRINT_FILE):
|
||||
@find project Dockerfile Makefile -type f -exec sha256sum {} \; | sort -k2 > $(FINGERPRINT_FILE)
|
||||
|
||||
# requirement 4: 'clean' goal to remove all generated test fixtures
|
||||
clean:
|
||||
rm -rf $(BIN) Dockerfile.sha256 $(FINGERPRINT_FILE)
|
||||
|
||||
.PHONY: tools tools-check build debug clean
|
||||
9
syft/file/cataloger/executable/testdata/toolchains/clang/project/Makefile
vendored
Normal file
9
syft/file/cataloger/executable/testdata/toolchains/clang/project/Makefile
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
BIN=../bin
|
||||
|
||||
all: $(BIN)/hello_clang
|
||||
|
||||
$(BIN)/hello_clang: hello.c
|
||||
clang hello.c -o $(BIN)/hello_clang
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)/hello_clang
|
||||
6
syft/file/cataloger/executable/testdata/toolchains/clang/project/hello.c
vendored
Normal file
6
syft/file/cataloger/executable/testdata/toolchains/clang/project/hello.c
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello, World!\n");
|
||||
return 0;
|
||||
}
|
||||
1
syft/file/cataloger/executable/testdata/toolchains/fortran/Dockerfile
vendored
Normal file
1
syft/file/cataloger/executable/testdata/toolchains/fortran/Dockerfile
vendored
Normal file
@ -0,0 +1 @@
|
||||
FROM gcc:13.4.0
|
||||
39
syft/file/cataloger/executable/testdata/toolchains/fortran/Makefile
vendored
Normal file
39
syft/file/cataloger/executable/testdata/toolchains/fortran/Makefile
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-toolchain-fortran-build-tools:latest
|
||||
FINGERPRINT_FILE=$(BIN).fingerprint
|
||||
|
||||
ifndef BIN
|
||||
$(error BIN is not set)
|
||||
endif
|
||||
|
||||
.DEFAULT_GOAL := fixtures
|
||||
|
||||
# requirement 1: 'fixtures' goal to generate any and all test fixtures
|
||||
fixtures: build
|
||||
|
||||
# requirement 2: 'fingerprint' goal to determine if the fixture input that indicates any existing cache should be busted
|
||||
fingerprint: $(FINGERPRINT_FILE)
|
||||
|
||||
tools-check:
|
||||
@sha256sum -c Dockerfile.sha256 || (echo "Tools Dockerfile has changed" && exit 1)
|
||||
|
||||
tools:
|
||||
@(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || (docker build --platform linux/amd64 -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
|
||||
|
||||
build: tools
|
||||
@mkdir -p $(BIN)
|
||||
docker run --platform linux/amd64 -i -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) make
|
||||
|
||||
debug:
|
||||
docker run --platform linux/amd64 -i --rm -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) bash
|
||||
|
||||
# requirement 3: we always need to recalculate the fingerprint based on source regardless of any existing fingerprint
|
||||
.PHONY: $(FINGERPRINT_FILE)
|
||||
$(FINGERPRINT_FILE):
|
||||
@find project Dockerfile Makefile -type f -exec sha256sum {} \; | sort -k2 > $(FINGERPRINT_FILE)
|
||||
|
||||
# requirement 4: 'clean' goal to remove all generated test fixtures
|
||||
clean:
|
||||
rm -rf $(BIN) Dockerfile.sha256 $(FINGERPRINT_FILE)
|
||||
|
||||
.PHONY: tools tools-check build debug clean
|
||||
9
syft/file/cataloger/executable/testdata/toolchains/fortran/project/Makefile
vendored
Normal file
9
syft/file/cataloger/executable/testdata/toolchains/fortran/project/Makefile
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
BIN=../bin
|
||||
|
||||
all: $(BIN)/hello_fortran
|
||||
|
||||
$(BIN)/hello_fortran: hello.f90
|
||||
gfortran hello.f90 -o $(BIN)/hello_fortran
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)/hello_fortran
|
||||
3
syft/file/cataloger/executable/testdata/toolchains/fortran/project/hello.f90
vendored
Normal file
3
syft/file/cataloger/executable/testdata/toolchains/fortran/project/hello.f90
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
program hello
|
||||
print *, "Hello, World!"
|
||||
end program hello
|
||||
1
syft/file/cataloger/executable/testdata/toolchains/gcc/Dockerfile
vendored
Normal file
1
syft/file/cataloger/executable/testdata/toolchains/gcc/Dockerfile
vendored
Normal file
@ -0,0 +1 @@
|
||||
FROM gcc:13.4.0
|
||||
39
syft/file/cataloger/executable/testdata/toolchains/gcc/Makefile
vendored
Normal file
39
syft/file/cataloger/executable/testdata/toolchains/gcc/Makefile
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-toolchain-gcc-build-tools:latest
|
||||
FINGERPRINT_FILE=$(BIN).fingerprint
|
||||
|
||||
ifndef BIN
|
||||
$(error BIN is not set)
|
||||
endif
|
||||
|
||||
.DEFAULT_GOAL := fixtures
|
||||
|
||||
# requirement 1: 'fixtures' goal to generate any and all test fixtures
|
||||
fixtures: build
|
||||
|
||||
# requirement 2: 'fingerprint' goal to determine if the fixture input that indicates any existing cache should be busted
|
||||
fingerprint: $(FINGERPRINT_FILE)
|
||||
|
||||
tools-check:
|
||||
@sha256sum -c Dockerfile.sha256 || (echo "Tools Dockerfile has changed" && exit 1)
|
||||
|
||||
tools:
|
||||
@(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || (docker build --platform linux/amd64 -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
|
||||
|
||||
build: tools
|
||||
@mkdir -p $(BIN)
|
||||
docker run --platform linux/amd64 -i -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) make
|
||||
|
||||
debug:
|
||||
docker run --platform linux/amd64 -i --rm -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) bash
|
||||
|
||||
# requirement 3: we always need to recalculate the fingerprint based on source regardless of any existing fingerprint
|
||||
.PHONY: $(FINGERPRINT_FILE)
|
||||
$(FINGERPRINT_FILE):
|
||||
@find project Dockerfile Makefile -type f -exec sha256sum {} \; | sort -k2 > $(FINGERPRINT_FILE)
|
||||
|
||||
# requirement 4: 'clean' goal to remove all generated test fixtures
|
||||
clean:
|
||||
rm -rf $(BIN) Dockerfile.sha256 $(FINGERPRINT_FILE)
|
||||
|
||||
.PHONY: tools tools-check build debug clean
|
||||
9
syft/file/cataloger/executable/testdata/toolchains/gcc/project/Makefile
vendored
Normal file
9
syft/file/cataloger/executable/testdata/toolchains/gcc/project/Makefile
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
BIN=../bin
|
||||
|
||||
all: $(BIN)/hello_gcc
|
||||
|
||||
$(BIN)/hello_gcc: hello.c
|
||||
gcc hello.c -o $(BIN)/hello_gcc
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)/hello_gcc
|
||||
6
syft/file/cataloger/executable/testdata/toolchains/gcc/project/hello.c
vendored
Normal file
6
syft/file/cataloger/executable/testdata/toolchains/gcc/project/hello.c
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello, World!\n");
|
||||
return 0;
|
||||
}
|
||||
3
syft/file/cataloger/executable/testdata/toolchains/gold/Dockerfile
vendored
Normal file
3
syft/file/cataloger/executable/testdata/toolchains/gold/Dockerfile
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
FROM alpine:3.21
|
||||
|
||||
RUN apk add --no-cache gcc binutils-gold musl-dev make
|
||||
39
syft/file/cataloger/executable/testdata/toolchains/gold/Makefile
vendored
Normal file
39
syft/file/cataloger/executable/testdata/toolchains/gold/Makefile
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-toolchain-gold-build-tools:latest
|
||||
FINGERPRINT_FILE=$(BIN).fingerprint
|
||||
|
||||
ifndef BIN
|
||||
$(error BIN is not set)
|
||||
endif
|
||||
|
||||
.DEFAULT_GOAL := fixtures
|
||||
|
||||
# requirement 1: 'fixtures' goal to generate any and all test fixtures
|
||||
fixtures: build
|
||||
|
||||
# requirement 2: 'fingerprint' goal to determine if the fixture input that indicates any existing cache should be busted
|
||||
fingerprint: $(FINGERPRINT_FILE)
|
||||
|
||||
tools-check:
|
||||
@sha256sum -c Dockerfile.sha256 || (echo "Tools Dockerfile has changed" && exit 1)
|
||||
|
||||
tools:
|
||||
@(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || (docker build --platform linux/amd64 -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
|
||||
|
||||
build: tools
|
||||
@mkdir -p $(BIN)
|
||||
docker run --platform linux/amd64 -i -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) make
|
||||
|
||||
debug:
|
||||
docker run --platform linux/amd64 -i --rm -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) bash
|
||||
|
||||
# requirement 3: we always need to recalculate the fingerprint based on source regardless of any existing fingerprint
|
||||
.PHONY: $(FINGERPRINT_FILE)
|
||||
$(FINGERPRINT_FILE):
|
||||
@find project Dockerfile Makefile -type f -exec sha256sum {} \; | sort -k2 > $(FINGERPRINT_FILE)
|
||||
|
||||
# requirement 4: 'clean' goal to remove all generated test fixtures
|
||||
clean:
|
||||
rm -rf $(BIN) Dockerfile.sha256 $(FINGERPRINT_FILE)
|
||||
|
||||
.PHONY: tools tools-check build debug clean
|
||||
9
syft/file/cataloger/executable/testdata/toolchains/gold/project/Makefile
vendored
Normal file
9
syft/file/cataloger/executable/testdata/toolchains/gold/project/Makefile
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
BIN=../bin
|
||||
|
||||
all: $(BIN)/hello_gold
|
||||
|
||||
$(BIN)/hello_gold: hello.c
|
||||
gcc hello.c -fuse-ld=gold -o $(BIN)/hello_gold
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)/hello_gold
|
||||
6
syft/file/cataloger/executable/testdata/toolchains/gold/project/hello.c
vendored
Normal file
6
syft/file/cataloger/executable/testdata/toolchains/gold/project/hello.c
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello, World!\n");
|
||||
return 0;
|
||||
}
|
||||
6
syft/file/cataloger/executable/testdata/toolchains/lld/Dockerfile
vendored
Normal file
6
syft/file/cataloger/executable/testdata/toolchains/lld/Dockerfile
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
FROM alpine:3.21
|
||||
|
||||
RUN apk add --no-cache clang18 lld musl-dev make
|
||||
|
||||
# create symlink so 'clang' command works (Alpine installs as clang-18)
|
||||
RUN ln -s /usr/bin/clang-18 /usr/bin/clang
|
||||
39
syft/file/cataloger/executable/testdata/toolchains/lld/Makefile
vendored
Normal file
39
syft/file/cataloger/executable/testdata/toolchains/lld/Makefile
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-toolchain-lld-build-tools:latest
|
||||
FINGERPRINT_FILE=$(BIN).fingerprint
|
||||
|
||||
ifndef BIN
|
||||
$(error BIN is not set)
|
||||
endif
|
||||
|
||||
.DEFAULT_GOAL := fixtures
|
||||
|
||||
# requirement 1: 'fixtures' goal to generate any and all test fixtures
|
||||
fixtures: build
|
||||
|
||||
# requirement 2: 'fingerprint' goal to determine if the fixture input that indicates any existing cache should be busted
|
||||
fingerprint: $(FINGERPRINT_FILE)
|
||||
|
||||
tools-check:
|
||||
@sha256sum -c Dockerfile.sha256 || (echo "Tools Dockerfile has changed" && exit 1)
|
||||
|
||||
tools:
|
||||
@(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || (docker build --platform linux/amd64 -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
|
||||
|
||||
build: tools
|
||||
@mkdir -p $(BIN)
|
||||
docker run --platform linux/amd64 -i -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) make
|
||||
|
||||
debug:
|
||||
docker run --platform linux/amd64 -i --rm -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) bash
|
||||
|
||||
# requirement 3: we always need to recalculate the fingerprint based on source regardless of any existing fingerprint
|
||||
.PHONY: $(FINGERPRINT_FILE)
|
||||
$(FINGERPRINT_FILE):
|
||||
@find project Dockerfile Makefile -type f -exec sha256sum {} \; | sort -k2 > $(FINGERPRINT_FILE)
|
||||
|
||||
# requirement 4: 'clean' goal to remove all generated test fixtures
|
||||
clean:
|
||||
rm -rf $(BIN) Dockerfile.sha256 $(FINGERPRINT_FILE)
|
||||
|
||||
.PHONY: tools tools-check build debug clean
|
||||
9
syft/file/cataloger/executable/testdata/toolchains/lld/project/Makefile
vendored
Normal file
9
syft/file/cataloger/executable/testdata/toolchains/lld/project/Makefile
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
BIN=../bin
|
||||
|
||||
all: $(BIN)/hello_lld
|
||||
|
||||
$(BIN)/hello_lld: hello.c
|
||||
clang hello.c -fuse-ld=lld -o $(BIN)/hello_lld
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)/hello_lld
|
||||
6
syft/file/cataloger/executable/testdata/toolchains/lld/project/hello.c
vendored
Normal file
6
syft/file/cataloger/executable/testdata/toolchains/lld/project/hello.c
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello, World!\n");
|
||||
return 0;
|
||||
}
|
||||
3
syft/file/cataloger/executable/testdata/toolchains/mold/Dockerfile
vendored
Normal file
3
syft/file/cataloger/executable/testdata/toolchains/mold/Dockerfile
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
FROM alpine:3.21
|
||||
|
||||
RUN apk add --no-cache gcc mold musl-dev make
|
||||
39
syft/file/cataloger/executable/testdata/toolchains/mold/Makefile
vendored
Normal file
39
syft/file/cataloger/executable/testdata/toolchains/mold/Makefile
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-toolchain-mold-build-tools:latest
|
||||
FINGERPRINT_FILE=$(BIN).fingerprint
|
||||
|
||||
ifndef BIN
|
||||
$(error BIN is not set)
|
||||
endif
|
||||
|
||||
.DEFAULT_GOAL := fixtures
|
||||
|
||||
# requirement 1: 'fixtures' goal to generate any and all test fixtures
|
||||
fixtures: build
|
||||
|
||||
# requirement 2: 'fingerprint' goal to determine if the fixture input that indicates any existing cache should be busted
|
||||
fingerprint: $(FINGERPRINT_FILE)
|
||||
|
||||
tools-check:
|
||||
@sha256sum -c Dockerfile.sha256 || (echo "Tools Dockerfile has changed" && exit 1)
|
||||
|
||||
tools:
|
||||
@(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || (docker build --platform linux/amd64 -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
|
||||
|
||||
build: tools
|
||||
@mkdir -p $(BIN)
|
||||
docker run --platform linux/amd64 -i -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) make
|
||||
|
||||
debug:
|
||||
docker run --platform linux/amd64 -i --rm -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) bash
|
||||
|
||||
# requirement 3: we always need to recalculate the fingerprint based on source regardless of any existing fingerprint
|
||||
.PHONY: $(FINGERPRINT_FILE)
|
||||
$(FINGERPRINT_FILE):
|
||||
@find project Dockerfile Makefile -type f -exec sha256sum {} \; | sort -k2 > $(FINGERPRINT_FILE)
|
||||
|
||||
# requirement 4: 'clean' goal to remove all generated test fixtures
|
||||
clean:
|
||||
rm -rf $(BIN) Dockerfile.sha256 $(FINGERPRINT_FILE)
|
||||
|
||||
.PHONY: tools tools-check build debug clean
|
||||
9
syft/file/cataloger/executable/testdata/toolchains/mold/project/Makefile
vendored
Normal file
9
syft/file/cataloger/executable/testdata/toolchains/mold/project/Makefile
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
BIN=../bin
|
||||
|
||||
all: $(BIN)/hello_mold
|
||||
|
||||
$(BIN)/hello_mold: hello.c
|
||||
gcc hello.c -fuse-ld=mold -o $(BIN)/hello_mold
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)/hello_mold
|
||||
6
syft/file/cataloger/executable/testdata/toolchains/mold/project/hello.c
vendored
Normal file
6
syft/file/cataloger/executable/testdata/toolchains/mold/project/hello.c
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello, World!\n");
|
||||
return 0;
|
||||
}
|
||||
1
syft/file/cataloger/executable/testdata/toolchains/rust/Dockerfile
vendored
Normal file
1
syft/file/cataloger/executable/testdata/toolchains/rust/Dockerfile
vendored
Normal file
@ -0,0 +1 @@
|
||||
FROM rust:1.83.0
|
||||
39
syft/file/cataloger/executable/testdata/toolchains/rust/Makefile
vendored
Normal file
39
syft/file/cataloger/executable/testdata/toolchains/rust/Makefile
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-toolchain-rust-build-tools:latest
|
||||
FINGERPRINT_FILE=$(BIN).fingerprint
|
||||
|
||||
ifndef BIN
|
||||
$(error BIN is not set)
|
||||
endif
|
||||
|
||||
.DEFAULT_GOAL := fixtures
|
||||
|
||||
# requirement 1: 'fixtures' goal to generate any and all test fixtures
|
||||
fixtures: build
|
||||
|
||||
# requirement 2: 'fingerprint' goal to determine if the fixture input that indicates any existing cache should be busted
|
||||
fingerprint: $(FINGERPRINT_FILE)
|
||||
|
||||
tools-check:
|
||||
@sha256sum -c Dockerfile.sha256 || (echo "Tools Dockerfile has changed" && exit 1)
|
||||
|
||||
tools:
|
||||
@(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || (docker build --platform linux/amd64 -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
|
||||
|
||||
build: tools
|
||||
@mkdir -p $(BIN)
|
||||
docker run --platform linux/amd64 -i -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) make
|
||||
|
||||
debug:
|
||||
docker run --platform linux/amd64 -i --rm -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) bash
|
||||
|
||||
# requirement 3: we always need to recalculate the fingerprint based on source regardless of any existing fingerprint
|
||||
.PHONY: $(FINGERPRINT_FILE)
|
||||
$(FINGERPRINT_FILE):
|
||||
@find project Dockerfile Makefile -type f -exec sha256sum {} \; | sort -k2 > $(FINGERPRINT_FILE)
|
||||
|
||||
# requirement 4: 'clean' goal to remove all generated test fixtures
|
||||
clean:
|
||||
rm -rf $(BIN) Dockerfile.sha256 $(FINGERPRINT_FILE)
|
||||
|
||||
.PHONY: tools tools-check build debug clean
|
||||
9
syft/file/cataloger/executable/testdata/toolchains/rust/project/Makefile
vendored
Normal file
9
syft/file/cataloger/executable/testdata/toolchains/rust/project/Makefile
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
BIN=../bin
|
||||
|
||||
all: $(BIN)/hello_rust
|
||||
|
||||
$(BIN)/hello_rust: hello.rs
|
||||
rustc hello.rs -o $(BIN)/hello_rust
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)/hello_rust
|
||||
3
syft/file/cataloger/executable/testdata/toolchains/rust/project/hello.rs
vendored
Normal file
3
syft/file/cataloger/executable/testdata/toolchains/rust/project/hello.rs
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("Hello, World!");
|
||||
}
|
||||
180
syft/file/cataloger/executable/toolchains.go
Normal file
180
syft/file/cataloger/executable/toolchains.go
Normal file
@ -0,0 +1,180 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"debug/buildinfo"
|
||||
"debug/elf"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
// TODO: additional toolchain detectors we'd like to add. Each entry notes the signal location and the
|
||||
// component it yields.
|
||||
//
|
||||
// ELF (.comment / notes / symbols — same mechanism as the detectors below):
|
||||
// 1. rustc commit hash: scan rodata for "/rustc/<40-hex>/library/" panic paths; resolves rust provenance
|
||||
// for stripped or pre-1.73 binaries (where the .comment producer string is absent). needs a
|
||||
// commit->version lookup table to turn the hash into a semver. component: compiler.
|
||||
// 2. gdc (GNU D): "GNU D" marker alongside the GCC version in .comment. component: compiler.
|
||||
// 3. Swift (Linux): presence of "swift5_*" sections marks Swift; version only when a "swiftlang-"
|
||||
// producer string is present (debug builds). component: compiler.
|
||||
// 4. Haskell GHC: RTS symbols (hs_init, stg_*) for identity; derive an approximate version from the
|
||||
// embedded "base_<x.y.z>" package symbol. component: compiler.
|
||||
// 5. identity-only (no reliable version) via runtime symbol prefixes: OCaml (caml_*), Nim (NimMain),
|
||||
// Crystal (__crystal_main), FreePascal (FPC_*), ldc/dmd D (_Dmain). component: compiler.
|
||||
// 6. GNU assembler (GNU AS): only present in DWARF .debug_info producer strings, NOT .comment, so it
|
||||
// requires DWARF parsing and is absent in stripped builds. component: assembler.
|
||||
// 7. GNU ld (BFD): leaves no self-identifying marker; can only be inferred by elimination (ELF with a
|
||||
// compiler .comment but no lld/mold/gold evidence) at low confidence and with no version.
|
||||
//
|
||||
// PE (Windows — needs debug/pe, not covered by this ELF-only file):
|
||||
// 8. MSVC link.exe + cl.exe: parse the Rich Header (the "DanS".."Rich" block, XOR-decoded into
|
||||
// ProdID/build pairs); map build numbers to Visual Studio/linker versions. components: linker + compiler.
|
||||
// 9. .NET / CLR: COR20 header (optional-header DataDirectory[14]) marks a managed assembly; read the
|
||||
// "TargetFrameworkAttribute" string (e.g. ".NETCoreApp,Version=v8.0") for the runtime version.
|
||||
// component: runtime. (NativeAOT .NET is a native binary with runtime symbols only and no clean version.)
|
||||
//
|
||||
// Mach-O (macOS — needs debug/macho):
|
||||
// 10. Swift: presence of "__swift5_*" sections; version via the "swiftlang-" producer when present.
|
||||
// component: compiler.
|
||||
|
||||
var (
|
||||
clangVersionPattern = regexp.MustCompile(`clang version (\d+\.\d+\.\d+)`)
|
||||
gccVersionPattern = regexp.MustCompile(`GCC: \([^)]+\) (\d+\.\d+\.\d+)`)
|
||||
// rustc embeds its own producer string in .comment for binaries built with rust >= 1.73 (e.g.
|
||||
// "rustc version 1.83.0 (90b35a623 2024-11-26)"). The shipped libstd objects instead carry the
|
||||
// "clang LLVM (rustc version ...)" form, so accept both.
|
||||
rustcVersionPattern = regexp.MustCompile(`rustc version (\d+\.\d+\.\d+)`)
|
||||
// LLVM lld writes "Linker: LLD <version>" into .comment (see https://lld.llvm.org/).
|
||||
lldVersionPattern = regexp.MustCompile(`Linker: LLD (\d+\.\d+\.\d+)`)
|
||||
// mold writes "mold <version> (...; compatible with GNU ld)" into .comment.
|
||||
moldVersionPattern = regexp.MustCompile(`mold (\d+\.\d+\.\d+)`)
|
||||
// GNU gold writes a "gold <version>" descriptor into the .note.gnu.gold-version note section.
|
||||
goldVersionPattern = regexp.MustCompile(`gold (\d+\.\d+)`)
|
||||
)
|
||||
|
||||
// golangToolchainEvidence attempts to extract Go toolchain information from the binary build info.
|
||||
func golangToolchainEvidence(reader io.ReaderAt) *file.Toolchain {
|
||||
bi, err := buildinfo.Read(reader)
|
||||
if err != nil || bi == nil {
|
||||
// not a golang binary
|
||||
return nil
|
||||
}
|
||||
return &file.Toolchain{
|
||||
Name: "go",
|
||||
Version: bi.GoVersion,
|
||||
Component: file.ToolchainComponentCompiler,
|
||||
}
|
||||
}
|
||||
|
||||
// cToolchainEvidence attempts to extract C/C++/Fortran compiler information from the ELF .comment section.
|
||||
// This detects GCC, Clang, and gfortran compilers based on their version strings.
|
||||
func cToolchainEvidence(comments []string, symbols *strset.Set) *file.Toolchain {
|
||||
for _, comment := range comments {
|
||||
// check for clang first since clang binaries often have both GCC and clang entries
|
||||
// (clang includes GCC compatibility info)
|
||||
if match := clangVersionPattern.FindStringSubmatch(comment); match != nil {
|
||||
return &file.Toolchain{
|
||||
Name: "clang",
|
||||
Version: match[1],
|
||||
Component: file.ToolchainComponentCompiler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, comment := range comments {
|
||||
if match := gccVersionPattern.FindStringSubmatch(comment); match != nil {
|
||||
// gfortran is a GCC frontend and shares the GCC version string in .comment, so the only way
|
||||
// to distinguish a Fortran build from a C build is by the presence of libgfortran runtime symbols.
|
||||
name := "gcc"
|
||||
if hasFortranEvidence(symbols) {
|
||||
name = "gfortran"
|
||||
}
|
||||
return &file.Toolchain{
|
||||
Name: name,
|
||||
Version: match[1],
|
||||
Component: file.ToolchainComponentCompiler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// rustToolchainEvidence attempts to extract Rust compiler information from the ELF .comment section.
|
||||
func rustToolchainEvidence(comments []string) *file.Toolchain {
|
||||
for _, comment := range comments {
|
||||
if match := rustcVersionPattern.FindStringSubmatch(comment); match != nil {
|
||||
return &file.Toolchain{
|
||||
Name: "rust",
|
||||
Version: match[1],
|
||||
Component: file.ToolchainComponentCompiler,
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// linkerToolchainEvidence attempts to extract linker information from the binary. lld and mold leave a
|
||||
// version string in the ELF .comment section, while gold writes a dedicated .note.gnu.gold-version note.
|
||||
// GNU ld (BFD) leaves no self-identifying marker, so it cannot be detected here.
|
||||
func linkerToolchainEvidence(f *elf.File, comments []string) *file.Toolchain {
|
||||
for _, comment := range comments {
|
||||
if match := lldVersionPattern.FindStringSubmatch(comment); match != nil {
|
||||
return &file.Toolchain{
|
||||
Name: "lld",
|
||||
Version: match[1],
|
||||
Component: file.ToolchainComponentLinker,
|
||||
}
|
||||
}
|
||||
if match := moldVersionPattern.FindStringSubmatch(comment); match != nil {
|
||||
return &file.Toolchain{
|
||||
Name: "mold",
|
||||
Version: match[1],
|
||||
Component: file.ToolchainComponentLinker,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if section := f.Section(".note.gnu.gold-version"); section != nil {
|
||||
data, err := section.Data()
|
||||
if err == nil {
|
||||
if match := goldVersionPattern.FindStringSubmatch(string(data)); match != nil {
|
||||
return &file.Toolchain{
|
||||
Name: "gold",
|
||||
Version: match[1],
|
||||
Component: file.ToolchainComponentLinker,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// elfComments returns the null-delimited strings within the ELF .comment section, which may contain
|
||||
// multiple producer entries (compiler, assembler, linker, etc.).
|
||||
func elfComments(f *elf.File) []string {
|
||||
section := f.Section(".comment")
|
||||
if section == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := section.Data()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// the .comment section contains null-terminated strings
|
||||
return strings.Split(string(data), "\x00")
|
||||
}
|
||||
|
||||
// hasFortranEvidence reports whether the binary references the libgfortran runtime, which indicates a
|
||||
// gfortran build (MAIN__ is the real Fortran program entry, and _gfortran_* are runtime calls).
|
||||
func hasFortranEvidence(symbols *strset.Set) bool {
|
||||
return symbols.HasAny("MAIN__", "_gfortran_set_args", "_gfortran_set_options", "_gfortran_st_write")
|
||||
}
|
||||
113
syft/file/cataloger/executable/toolchains_test.go
Normal file
113
syft/file/cataloger/executable/toolchains_test.go
Normal file
@ -0,0 +1,113 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
|
||||
// hasGoToolchain is a test helper to check whether the go toolchain was detected.
|
||||
func hasGoToolchain(toolchains []file.Toolchain) bool {
|
||||
for _, tc := range toolchains {
|
||||
if tc.Name == "go" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func Test_elfToolchains(t *testing.T) {
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("testdata/toolchains", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
compiler := file.ToolchainComponentCompiler
|
||||
linker := file.ToolchainComponentLinker
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
want []file.Toolchain
|
||||
}{
|
||||
{
|
||||
name: "gcc: compiler only",
|
||||
fixture: "gcc/bin/hello_gcc",
|
||||
want: []file.Toolchain{
|
||||
{Name: "gcc", Version: "13.4.0", Component: compiler},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "clang: compiler only",
|
||||
fixture: "clang/bin/hello_clang",
|
||||
want: []file.Toolchain{
|
||||
{Name: "clang", Version: "18.1.8", Component: compiler},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "lld: clang compiler + lld linker",
|
||||
fixture: "lld/bin/hello_lld",
|
||||
want: []file.Toolchain{
|
||||
{Name: "clang", Version: "18.1.8", Component: compiler},
|
||||
{Name: "lld", Version: "19.1.4", Component: linker},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mold: gcc compiler + mold linker",
|
||||
fixture: "mold/bin/hello_mold",
|
||||
want: []file.Toolchain{
|
||||
{Name: "gcc", Version: "14.2.0", Component: compiler},
|
||||
{Name: "mold", Version: "2.34.1", Component: linker},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "gold: gcc compiler + gold linker",
|
||||
fixture: "gold/bin/hello_gold",
|
||||
want: []file.Toolchain{
|
||||
{Name: "gcc", Version: "14.2.0", Component: compiler},
|
||||
{Name: "gold", Version: "1.16", Component: linker},
|
||||
},
|
||||
},
|
||||
{
|
||||
// rust binaries also carry a GCC producer string from the gcc-compiled C runtime glue that is
|
||||
// linked in, so both compilers are reported (akin to how cgo binaries report go and gcc).
|
||||
name: "rust: gcc glue + rustc",
|
||||
fixture: "rust/bin/hello_rust",
|
||||
want: []file.Toolchain{
|
||||
{Name: "gcc", Version: "12.2.0", Component: compiler},
|
||||
{Name: "rust", Version: "1.83.0", Component: compiler},
|
||||
},
|
||||
},
|
||||
{
|
||||
// gfortran shares the GCC version string in .comment, so it is only distinguishable from a
|
||||
// C build by the presence of libgfortran runtime symbols (gcc gets relabeled to gfortran).
|
||||
name: "fortran: gfortran compiler only",
|
||||
fixture: "fortran/bin/hello_fortran",
|
||||
want: []file.Toolchain{
|
||||
{Name: "gfortran", Version: "13.4.0", Component: compiler},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
reader := readerForFixture(t, tt.fixture)
|
||||
f, err := elf.NewFile(reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
got := elfToolchains(reader, f)
|
||||
|
||||
if d := cmp.Diff(tt.want, got); d != "" {
|
||||
t.Errorf("elfToolchains() mismatch (-want +got):\n%s", d)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -6,9 +6,16 @@ type (
|
||||
|
||||
// RelocationReadOnly indicates the RELRO security protection level applied to an ELF binary.
|
||||
RelocationReadOnly string
|
||||
|
||||
// ToolchainComponent represents which part of the toolchain an entry describes (e.g. compiler, linker).
|
||||
// This can be expanded in the future to include assemblers, runtimes, and other toolchain components.
|
||||
ToolchainComponent string
|
||||
)
|
||||
|
||||
const (
|
||||
ToolchainComponentCompiler ToolchainComponent = "compiler"
|
||||
ToolchainComponentLinker ToolchainComponent = "linker"
|
||||
|
||||
ELF ExecutableFormat = "elf" // Executable and Linkable Format used on Unix-like systems
|
||||
MachO ExecutableFormat = "macho" // Mach object file format used on macOS and iOS
|
||||
PE ExecutableFormat = "pe" // Portable Executable format used on Windows
|
||||
@ -34,6 +41,20 @@ type Executable struct {
|
||||
|
||||
// ELFSecurityFeatures contains ELF-specific security hardening information when Format is ELF.
|
||||
ELFSecurityFeatures *ELFSecurityFeatures `json:"elfSecurityFeatures,omitempty" yaml:"elfSecurityFeatures" mapstructure:"elfSecurityFeatures"`
|
||||
|
||||
// Toolchains captures information about the compiler, linker, runtime, or other toolchains used to build (or otherwise exist within) the executable.
|
||||
Toolchains []Toolchain `json:"toolchains,omitempty" yaml:"toolchains" mapstructure:"toolchains"`
|
||||
}
|
||||
|
||||
type Toolchain struct {
|
||||
// Name is the name of the toolchain (e.g., "gcc", "clang", "rust", "lld", "GNU AS", etc.).
|
||||
Name string `json:"name" yaml:"name" mapstructure:"name"`
|
||||
|
||||
// Version is the version of the toolchain.
|
||||
Version string `json:"version,omitempty" yaml:"version,omitempty" mapstructure:"version"`
|
||||
|
||||
// Component indicates which part of the toolchain this represents (e.g., compiler, linker, assembler).
|
||||
Component ToolchainComponent `json:"component" yaml:"component" mapstructure:"component"`
|
||||
}
|
||||
|
||||
// ELFSecurityFeatures captures security hardening and protection mechanisms in ELF binaries.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user