mirror of
https://github.com/anchore/syft.git
synced 2026-02-12 02:26:42 +01:00
add gcc and clang toolchains
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
9bf4c5bdf9
commit
32946ec41f
@ -26,7 +26,7 @@ import (
|
||||
|
||||
type SymbolCaptureScope string
|
||||
|
||||
//type SymbolTypes string
|
||||
// type SymbolTypes string
|
||||
|
||||
const (
|
||||
SymbolScopeAll SymbolCaptureScope = "all" // any and all binaries
|
||||
@ -35,8 +35,8 @@ const (
|
||||
SymbolScopeGolang SymbolCaptureScope = "golang" // only binaries built with the golang toolchain
|
||||
SymbolScopeNone SymbolCaptureScope = "none" // do not capture any symbols
|
||||
|
||||
//SymbolTypeCode SymbolTypes = "code"
|
||||
//SymbolTypeData SymbolTypes = "data"
|
||||
// SymbolTypeCode SymbolTypes = "code"
|
||||
// SymbolTypeData SymbolTypes = "data"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@ -55,16 +55,16 @@ type SymbolConfig struct {
|
||||
// CaptureScope defines the scope of symbols to capture from executables (all binaries, libraries only, applications only, golang binaries only, or none).
|
||||
CaptureScope []SymbolCaptureScope `json:"capture" yaml:"capture" mapstructure:"capture"`
|
||||
|
||||
// Types are the types of Go symbols to capture, relative to `go tool nm` output (e.g. T, t, R, r, D, d, B, b, C, U, etc).
|
||||
// If empty, all symbol types are captured.
|
||||
Types []string
|
||||
|
||||
// Go configures Go-specific symbol capturing settings.
|
||||
Go GoSymbolConfig `json:"go" yaml:"go" mapstructure:"go"`
|
||||
}
|
||||
|
||||
// GoSymbolConfig holds settings specific to capturing symbols from binaries built with the golang toolchain.
|
||||
type GoSymbolConfig struct {
|
||||
// Types are the types of Go symbols to capture, relative to `go tool nm` output (e.g. T, t, R, r, D, d, B, b, C, U, etc).
|
||||
// If empty, all symbol types are captured.
|
||||
Types []string
|
||||
|
||||
// StandardLibrary indicates whether to capture Go standard library symbols (e.g. "fmt", "net/http", etc).
|
||||
StandardLibrary bool `json:"standard-library" yaml:"standard-library" mapstructure:"standard-library"`
|
||||
|
||||
@ -104,8 +104,8 @@ func DefaultConfig() Config {
|
||||
CaptureScope: []SymbolCaptureScope{
|
||||
SymbolScopeGolang,
|
||||
},
|
||||
Types: []string{"T", "t"},
|
||||
Go: GoSymbolConfig{
|
||||
Types: []string{"T", "t"},
|
||||
StandardLibrary: true,
|
||||
ExtendedStandardLibrary: true,
|
||||
ThirdPartyModules: true,
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"debug/buildinfo"
|
||||
"debug/elf"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@ -47,28 +45,10 @@ func findELFFeatures(data *file.Executable, reader unionreader.UnionReader, cfg
|
||||
func elfToolchains(reader unionreader.UnionReader, f *elf.File) []file.Toolchain {
|
||||
return includeNoneNil(
|
||||
golangToolchainEvidence(reader),
|
||||
cToolchainEvidence(f),
|
||||
)
|
||||
}
|
||||
|
||||
func shouldCaptureSymbols(data *file.Executable, cfg SymbolConfig) bool {
|
||||
// TODO: IMPLEMENT ME!
|
||||
return true
|
||||
}
|
||||
|
||||
// elfGolangToolchainEvidence attempts to extract Go toolchain information from the ELF file.
|
||||
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,
|
||||
Kind: file.ToolchainKindCompiler,
|
||||
}
|
||||
}
|
||||
|
||||
func includeNoneNil(evidence ...*file.Toolchain) []file.Toolchain {
|
||||
var toolchains []file.Toolchain
|
||||
for _, e := range evidence {
|
||||
@ -84,8 +64,18 @@ func elfNMSymbols(f *elf.File, cfg SymbolConfig, toolchains []file.Toolchain) []
|
||||
return captureElfGoSymbols(f, cfg)
|
||||
}
|
||||
|
||||
// TODO: capture other symbol types (non-go) based on the scope selection (lib, app, etc)
|
||||
return nil
|
||||
// include all symbols
|
||||
syms, err := f.Symbols()
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Trace("unable to read symbols from elf file")
|
||||
return nil
|
||||
}
|
||||
|
||||
var symbols []string
|
||||
for _, sym := range syms {
|
||||
symbols = append(symbols, sym.Name)
|
||||
}
|
||||
return symbols
|
||||
}
|
||||
|
||||
func captureElfGoSymbols(f *elf.File, cfg SymbolConfig) []string {
|
||||
@ -96,7 +86,7 @@ func captureElfGoSymbols(f *elf.File, cfg SymbolConfig) []string {
|
||||
}
|
||||
|
||||
var symbols []string
|
||||
filter := createGoSymbolFilter(cfg.Go)
|
||||
filter := createGoSymbolFilter(cfg)
|
||||
for _, sym := range syms {
|
||||
name, include := filter(sym.Name, elfSymbolType(sym, f.Sections))
|
||||
if include {
|
||||
|
||||
@ -227,26 +227,6 @@ func Test_elfHasExports(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_elfNMSymbols_nonGoReturnsNil(t *testing.T) {
|
||||
// for non-Go binaries, elfNMSymbols should return nil since we only support Go for now
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("test-fixtures/shared-info", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
f, err := elf.NewFile(readerForFixture(t, "bin/hello_linux"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// no Go toolchain present
|
||||
toolchains := []file.Toolchain{}
|
||||
cfg := SymbolConfig{}
|
||||
|
||||
symbols := elfNMSymbols(f, cfg, toolchains)
|
||||
assert.Nil(t, symbols, "expected nil symbols for non-Go binary")
|
||||
}
|
||||
|
||||
func Test_elfGoToolchainDetection(t *testing.T) {
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
@ -296,19 +276,21 @@ func Test_elfGoSymbolCapture(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
cfg GoSymbolConfig
|
||||
cfg SymbolConfig
|
||||
wantSymbols []string // exact symbol names that must be present
|
||||
wantMinSymbolCount int
|
||||
}{
|
||||
{
|
||||
name: "capture all symbol types",
|
||||
fixture: "bin/hello_linux",
|
||||
cfg: GoSymbolConfig{
|
||||
StandardLibrary: true,
|
||||
ExtendedStandardLibrary: true,
|
||||
ThirdPartyModules: true,
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
StandardLibrary: true,
|
||||
ExtendedStandardLibrary: true,
|
||||
ThirdPartyModules: true,
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
},
|
||||
},
|
||||
wantSymbols: []string{
|
||||
// stdlib - fmt package (used via fmt.Println)
|
||||
@ -331,10 +313,12 @@ func Test_elfGoSymbolCapture(t *testing.T) {
|
||||
{
|
||||
name: "capture only third-party symbols",
|
||||
fixture: "bin/hello_linux",
|
||||
cfg: GoSymbolConfig{
|
||||
ThirdPartyModules: true,
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ThirdPartyModules: true,
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
},
|
||||
},
|
||||
wantSymbols: []string{
|
||||
"github.com/davecgh/go-spew/spew.(*dumpState).dump",
|
||||
@ -345,10 +329,12 @@ func Test_elfGoSymbolCapture(t *testing.T) {
|
||||
{
|
||||
name: "capture only extended stdlib symbols",
|
||||
fixture: "bin/hello_linux",
|
||||
cfg: GoSymbolConfig{
|
||||
ExtendedStandardLibrary: true,
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExtendedStandardLibrary: true,
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
},
|
||||
},
|
||||
wantSymbols: []string{
|
||||
"golang.org/x/text/internal/language.Tag.String",
|
||||
@ -358,13 +344,15 @@ func Test_elfGoSymbolCapture(t *testing.T) {
|
||||
{
|
||||
name: "capture with text section types only",
|
||||
fixture: "bin/hello_linux",
|
||||
cfg: GoSymbolConfig{
|
||||
Types: []string{"T", "t"}, // text section (code) symbols
|
||||
StandardLibrary: true,
|
||||
ExtendedStandardLibrary: true,
|
||||
ThirdPartyModules: true,
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
cfg: SymbolConfig{
|
||||
Types: []string{"T", "t"}, // text section (code) symbols
|
||||
Go: GoSymbolConfig{
|
||||
StandardLibrary: true,
|
||||
ExtendedStandardLibrary: true,
|
||||
ThirdPartyModules: true,
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
},
|
||||
},
|
||||
wantSymbols: []string{
|
||||
"encoding/json.Marshal",
|
||||
@ -379,7 +367,7 @@ func Test_elfGoSymbolCapture(t *testing.T) {
|
||||
f, err := elf.NewFile(reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
symbols := captureElfGoSymbols(f, SymbolConfig{Go: tt.cfg})
|
||||
symbols := captureElfGoSymbols(f, tt.cfg)
|
||||
symbolSet := make(map[string]struct{}, len(symbols))
|
||||
for _, sym := range symbols {
|
||||
symbolSet[sym] = struct{}{}
|
||||
@ -415,8 +403,8 @@ func Test_elfNMSymbols_goReturnsSymbols(t *testing.T) {
|
||||
{Name: "go", Version: "1.24", Kind: file.ToolchainKindCompiler},
|
||||
}
|
||||
cfg := SymbolConfig{
|
||||
Types: []string{"T", "t"},
|
||||
Go: GoSymbolConfig{
|
||||
Types: []string{"T", "t"},
|
||||
StandardLibrary: true,
|
||||
ExtendedStandardLibrary: true,
|
||||
ThirdPartyModules: true,
|
||||
|
||||
@ -31,7 +31,7 @@ const (
|
||||
// createGoSymbolFilter creates a filter function for Go symbols based on the provided configuration. This filter function
|
||||
// returns true if a symbol should be included based on its name and type. This also allows for modification of the symbol name
|
||||
// if necessary (e.g., normalization of vendored module paths). The returned name is only valid if the boolean is true.
|
||||
func createGoSymbolFilter(cfg GoSymbolConfig) func(string, string) (string, bool) {
|
||||
func createGoSymbolFilter(cfg SymbolConfig) func(string, string) (string, bool) {
|
||||
validNmTypes := buildNmTypes(cfg.Types)
|
||||
|
||||
return func(symName, symType string) (string, bool) {
|
||||
@ -47,13 +47,13 @@ func createGoSymbolFilter(cfg GoSymbolConfig) func(string, string) (string, bool
|
||||
|
||||
// filter based on exported/unexported symbol configuration
|
||||
exported := isExportedSymbol(symName)
|
||||
if !shouldIncludeByExportStatus(exported, cfg.ExportedSymbols, cfg.UnexportedSymbols) {
|
||||
if !shouldIncludeByExportStatus(exported, cfg.Go.ExportedSymbols, cfg.Go.UnexportedSymbols) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// handle type equality functions (e.g., type:.eq.myStruct)
|
||||
if isTypeEqualityFunction(symName) {
|
||||
if !cfg.TypeEqualityFunctions {
|
||||
if !cfg.Go.TypeEqualityFunctions {
|
||||
return "", false
|
||||
}
|
||||
return symName, true
|
||||
@ -61,21 +61,21 @@ func createGoSymbolFilter(cfg GoSymbolConfig) func(string, string) (string, bool
|
||||
|
||||
// handle GC shape stencil functions (e.g., go.shape.func())
|
||||
if isGCShapeStencil(symName) {
|
||||
if !cfg.GCShapeStencils {
|
||||
if !cfg.Go.GCShapeStencils {
|
||||
return "", false
|
||||
}
|
||||
return symName, true
|
||||
}
|
||||
|
||||
// normalize vendored module paths if configured
|
||||
symName = normalizeVendoredPath(symName, cfg.NormalizeVendoredModules)
|
||||
symName = normalizeVendoredPath(symName, cfg.Go.NormalizeVendoredModules)
|
||||
|
||||
// determine the package path for classification
|
||||
pkgPath := extractPackagePath(symName)
|
||||
|
||||
// handle extended stdlib (golang.org/x/*)
|
||||
if isExtendedStdlib(pkgPath) {
|
||||
if !cfg.ExtendedStandardLibrary {
|
||||
if !cfg.Go.ExtendedStandardLibrary {
|
||||
return "", false
|
||||
}
|
||||
return symName, true
|
||||
@ -83,14 +83,14 @@ func createGoSymbolFilter(cfg GoSymbolConfig) func(string, string) (string, bool
|
||||
|
||||
// handle stdlib packages
|
||||
if isStdlibPackage(pkgPath) {
|
||||
if !cfg.StandardLibrary {
|
||||
if !cfg.Go.StandardLibrary {
|
||||
return "", false
|
||||
}
|
||||
return symName, true
|
||||
}
|
||||
|
||||
// this is a third-party package
|
||||
if !cfg.ThirdPartyModules {
|
||||
if !cfg.Go.ThirdPartyModules {
|
||||
return "", false
|
||||
}
|
||||
return symName, true
|
||||
@ -151,7 +151,7 @@ func isGCShapeStencil(symName string) bool {
|
||||
|
||||
// normalizeVendoredPath removes the "vendor/" prefix from vendored module paths if normalization is enabled.
|
||||
func normalizeVendoredPath(symName string, normalize bool) string {
|
||||
if normalize && strings.HasPrefix(symName, vendorPrefix) {
|
||||
if normalize && isVendoredPath(symName) {
|
||||
return strings.TrimPrefix(symName, vendorPrefix)
|
||||
}
|
||||
return symName
|
||||
|
||||
@ -607,7 +607,7 @@ func Test_isStdlibPackage(t *testing.T) {
|
||||
func Test_createGoSymbolFilter(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg GoSymbolConfig
|
||||
cfg SymbolConfig
|
||||
symName string
|
||||
symType string
|
||||
wantName string
|
||||
@ -616,10 +616,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
// NM type filtering
|
||||
{
|
||||
name: "valid NM type with defaults",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: true,
|
||||
},
|
||||
},
|
||||
symName: "fmt.Println",
|
||||
symType: "T",
|
||||
@ -628,10 +630,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "invalid NM type with defaults",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: true,
|
||||
},
|
||||
},
|
||||
symName: "fmt.Println",
|
||||
symType: "X", // important!
|
||||
@ -640,11 +644,13 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "custom NM types - included",
|
||||
cfg: GoSymbolConfig{
|
||||
Types: []string{"T"},
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: true,
|
||||
cfg: SymbolConfig{
|
||||
Types: []string{"T"},
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: true,
|
||||
},
|
||||
},
|
||||
symName: "fmt.Println",
|
||||
symType: "T",
|
||||
@ -653,11 +659,13 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "custom NM types - excluded",
|
||||
cfg: GoSymbolConfig{
|
||||
Types: []string{"T"},
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: true,
|
||||
cfg: SymbolConfig{
|
||||
Types: []string{"T"},
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: true,
|
||||
},
|
||||
},
|
||||
symName: "fmt.Println",
|
||||
symType: "t",
|
||||
@ -668,10 +676,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
// floating point literal filtering
|
||||
{
|
||||
name: "floating point literal filtered",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: true,
|
||||
},
|
||||
},
|
||||
symName: "$f64.3fceb851eb851eb8",
|
||||
symType: "R",
|
||||
@ -682,10 +692,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
// export status filtering
|
||||
{
|
||||
name: "exported symbol with only exported enabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: false,
|
||||
StandardLibrary: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: false,
|
||||
StandardLibrary: true,
|
||||
},
|
||||
},
|
||||
symName: "fmt.Println",
|
||||
symType: "T",
|
||||
@ -694,10 +706,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "unexported symbol with only exported enabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: false,
|
||||
StandardLibrary: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: false,
|
||||
StandardLibrary: true,
|
||||
},
|
||||
},
|
||||
symName: "fmt.println",
|
||||
symType: "T",
|
||||
@ -708,10 +722,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
// type equality functions
|
||||
{
|
||||
name: "type equality function - enabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
TypeEqualityFunctions: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
TypeEqualityFunctions: true,
|
||||
},
|
||||
},
|
||||
symName: "type:.eq.myStruct",
|
||||
symType: "T",
|
||||
@ -720,10 +736,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "type equality function - disabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
TypeEqualityFunctions: false,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
TypeEqualityFunctions: false,
|
||||
},
|
||||
},
|
||||
symName: "type:.eq.myStruct",
|
||||
symType: "T",
|
||||
@ -734,10 +752,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
// GC shape stencils
|
||||
{
|
||||
name: "gc shape stencil - enabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
GCShapeStencils: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
GCShapeStencils: true,
|
||||
},
|
||||
},
|
||||
symName: "go.shape.func()",
|
||||
symType: "T",
|
||||
@ -746,10 +766,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "gc shape stencil - disabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
GCShapeStencils: false,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
GCShapeStencils: false,
|
||||
},
|
||||
},
|
||||
symName: "go.shape.func()",
|
||||
symType: "T",
|
||||
@ -758,10 +780,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "gc shape stencil embedded in generic - enabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
GCShapeStencils: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
GCShapeStencils: true,
|
||||
},
|
||||
},
|
||||
symName: "slices.partitionCmpFunc[go.shape.struct { Key string; Value int }]",
|
||||
symType: "T",
|
||||
@ -770,10 +794,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "gc shape stencil embedded in generic - disabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
GCShapeStencils: false,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
GCShapeStencils: false,
|
||||
},
|
||||
},
|
||||
symName: "slices.partitionCmpFunc[go.shape.struct { Key string; Value int }]",
|
||||
symType: "T",
|
||||
@ -784,11 +810,13 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
// vendored module normalization
|
||||
{
|
||||
name: "vendored path - normalization enabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
ThirdPartyModules: true,
|
||||
NormalizeVendoredModules: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
ThirdPartyModules: true,
|
||||
NormalizeVendoredModules: true,
|
||||
},
|
||||
},
|
||||
symName: "vendor/github.com/foo/bar.Baz",
|
||||
symType: "T",
|
||||
@ -797,11 +825,13 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "vendored path - normalization disabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
ThirdPartyModules: true,
|
||||
NormalizeVendoredModules: false,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
ThirdPartyModules: true,
|
||||
NormalizeVendoredModules: false,
|
||||
},
|
||||
},
|
||||
symName: "vendor/github.com/foo/bar.Baz",
|
||||
symType: "T",
|
||||
@ -812,10 +842,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
// extended stdlib
|
||||
{
|
||||
name: "extended stdlib - enabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
ExtendedStandardLibrary: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
ExtendedStandardLibrary: true,
|
||||
},
|
||||
},
|
||||
symName: "golang.org/x/net/html.Parse",
|
||||
symType: "T",
|
||||
@ -824,10 +856,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "extended stdlib - disabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
ExtendedStandardLibrary: false,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
ExtendedStandardLibrary: false,
|
||||
},
|
||||
},
|
||||
symName: "golang.org/x/net/html.Parse",
|
||||
symType: "T",
|
||||
@ -838,10 +872,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
// stdlib
|
||||
{
|
||||
name: "stdlib - enabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: true,
|
||||
},
|
||||
},
|
||||
symName: "fmt.Println",
|
||||
symType: "T",
|
||||
@ -850,10 +886,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "stdlib - disabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: false,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: false,
|
||||
},
|
||||
},
|
||||
symName: "fmt.Println",
|
||||
symType: "T",
|
||||
@ -862,10 +900,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "nested stdlib - enabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: true,
|
||||
},
|
||||
},
|
||||
symName: "net/http.ListenAndServe",
|
||||
symType: "T",
|
||||
@ -876,10 +916,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
// third party
|
||||
{
|
||||
name: "third party - enabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
ThirdPartyModules: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
ThirdPartyModules: true,
|
||||
},
|
||||
},
|
||||
symName: "github.com/spf13/cobra.Command",
|
||||
symType: "T",
|
||||
@ -888,10 +930,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "third party - disabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
ThirdPartyModules: false,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
ThirdPartyModules: false,
|
||||
},
|
||||
},
|
||||
symName: "github.com/spf13/cobra.Command",
|
||||
symType: "T",
|
||||
@ -902,10 +946,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
// main package (treated as stdlib)
|
||||
{
|
||||
name: "main package - stdlib enabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: true,
|
||||
},
|
||||
},
|
||||
symName: "main.main",
|
||||
symType: "T",
|
||||
@ -914,10 +960,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "main package - stdlib disabled",
|
||||
cfg: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: false,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
StandardLibrary: false,
|
||||
},
|
||||
},
|
||||
symName: "main.main",
|
||||
symType: "T",
|
||||
|
||||
@ -59,7 +59,7 @@ func findMachoFeatures(data *file.Executable, reader unionreader.UnionReader, cf
|
||||
data.HasExports = machoHasExports(f)
|
||||
}
|
||||
|
||||
data.Toolchains = machoToolchains(reader, f)
|
||||
data.Toolchains = machoToolchains(reader)
|
||||
if shouldCaptureSymbols(data, cfg) {
|
||||
symbols = machoNMSymbols(f, cfg, data.Toolchains)
|
||||
}
|
||||
@ -72,7 +72,7 @@ func findMachoFeatures(data *file.Executable, reader unionreader.UnionReader, cf
|
||||
return nil
|
||||
}
|
||||
|
||||
func machoToolchains(reader unionreader.UnionReader, f *macho.File) []file.Toolchain {
|
||||
func machoToolchains(reader unionreader.UnionReader) []file.Toolchain {
|
||||
return includeNoneNil(
|
||||
golangToolchainEvidence(reader),
|
||||
)
|
||||
@ -83,13 +83,17 @@ func machoNMSymbols(f *macho.File, cfg SymbolConfig, toolchains []file.Toolchain
|
||||
return captureMachoGoSymbols(f, cfg)
|
||||
}
|
||||
|
||||
// TODO: capture other symbol types (non-go) based on the scope selection (lib, app, etc)
|
||||
return nil
|
||||
// include all symbols
|
||||
var symbols []string
|
||||
for _, sym := range f.Symtab.Syms {
|
||||
symbols = append(symbols, sym.Name)
|
||||
}
|
||||
return symbols
|
||||
}
|
||||
|
||||
func captureMachoGoSymbols(f *macho.File, cfg SymbolConfig) []string {
|
||||
var symbols []string
|
||||
filter := createGoSymbolFilter(cfg.Go)
|
||||
filter := createGoSymbolFilter(cfg)
|
||||
for _, sym := range f.Symtab.Syms {
|
||||
name, include := filter(sym.Name, machoSymbolType(sym, f.Sections))
|
||||
if include {
|
||||
@ -99,15 +103,6 @@ func captureMachoGoSymbols(f *macho.File, cfg SymbolConfig) []string {
|
||||
return symbols
|
||||
}
|
||||
|
||||
func isGoToolchainPresent(toolchains []file.Toolchain) bool {
|
||||
for _, tc := range toolchains {
|
||||
if tc.Name == "go" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func machoSymbolType(s macho.Symbol, sections []*macho.Section) string {
|
||||
// stab (debugging) symbols get '-'
|
||||
if s.Type&machoNStab != 0 {
|
||||
@ -133,7 +128,7 @@ func machoSymbolType(s macho.Symbol, sections []*macho.Section) string {
|
||||
|
||||
// lowercase for local symbols, uppercase for external
|
||||
if !isExternal && typeChar != '-' && typeChar != '?' {
|
||||
typeChar = typeChar + 32 // convert to lowercase
|
||||
typeChar += 32 // convert to lowercase
|
||||
}
|
||||
|
||||
return string(typeChar)
|
||||
|
||||
@ -121,26 +121,6 @@ func Test_machoUniversal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_machoNMSymbols_nonGoReturnsNil(t *testing.T) {
|
||||
// for non-Go binaries, machoNMSymbols should return nil since we only support Go for now
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("test-fixtures/shared-info", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
f, err := macho.NewFile(readerForFixture(t, "bin/hello_mac"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// no Go toolchain present
|
||||
toolchains := []file.Toolchain{}
|
||||
cfg := SymbolConfig{}
|
||||
|
||||
symbols := machoNMSymbols(f, cfg, toolchains)
|
||||
assert.Nil(t, symbols, "expected nil symbols for non-Go binary")
|
||||
}
|
||||
|
||||
func Test_machoGoToolchainDetection(t *testing.T) {
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
@ -163,10 +143,8 @@ func Test_machoGoToolchainDetection(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
reader := readerForFixture(t, tt.fixture)
|
||||
f, err := macho.NewFile(reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
toolchains := machoToolchains(reader, f)
|
||||
toolchains := machoToolchains(reader)
|
||||
assert.Equal(t, tt.wantPresent, isGoToolchainPresent(toolchains))
|
||||
|
||||
if tt.wantPresent {
|
||||
@ -190,19 +168,21 @@ func Test_machoGoSymbolCapture(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
cfg GoSymbolConfig
|
||||
cfg SymbolConfig
|
||||
wantSymbols []string // exact symbol names that must be present
|
||||
wantMinSymbolCount int
|
||||
}{
|
||||
{
|
||||
name: "capture all symbol types",
|
||||
fixture: "bin/hello_mac",
|
||||
cfg: GoSymbolConfig{
|
||||
StandardLibrary: true,
|
||||
ExtendedStandardLibrary: true,
|
||||
ThirdPartyModules: true,
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
StandardLibrary: true,
|
||||
ExtendedStandardLibrary: true,
|
||||
ThirdPartyModules: true,
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
},
|
||||
},
|
||||
wantSymbols: []string{
|
||||
// stdlib - fmt package (used via fmt.Println)
|
||||
@ -225,10 +205,12 @@ func Test_machoGoSymbolCapture(t *testing.T) {
|
||||
{
|
||||
name: "capture only third-party symbols",
|
||||
fixture: "bin/hello_mac",
|
||||
cfg: GoSymbolConfig{
|
||||
ThirdPartyModules: true,
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ThirdPartyModules: true,
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
},
|
||||
},
|
||||
wantSymbols: []string{
|
||||
"github.com/davecgh/go-spew/spew.(*dumpState).dump",
|
||||
@ -239,10 +221,12 @@ func Test_machoGoSymbolCapture(t *testing.T) {
|
||||
{
|
||||
name: "capture only extended stdlib symbols",
|
||||
fixture: "bin/hello_mac",
|
||||
cfg: GoSymbolConfig{
|
||||
ExtendedStandardLibrary: true,
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
cfg: SymbolConfig{
|
||||
Go: GoSymbolConfig{
|
||||
ExtendedStandardLibrary: true,
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
},
|
||||
},
|
||||
wantSymbols: []string{
|
||||
"golang.org/x/text/internal/language.Tag.String",
|
||||
@ -252,13 +236,15 @@ func Test_machoGoSymbolCapture(t *testing.T) {
|
||||
{
|
||||
name: "capture with text section types only",
|
||||
fixture: "bin/hello_mac",
|
||||
cfg: GoSymbolConfig{
|
||||
Types: []string{"T", "t"}, // text section (code) symbols
|
||||
StandardLibrary: true,
|
||||
ExtendedStandardLibrary: true,
|
||||
ThirdPartyModules: true,
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
cfg: SymbolConfig{
|
||||
Types: []string{"T", "t"}, // text section (code) symbols
|
||||
Go: GoSymbolConfig{
|
||||
StandardLibrary: true,
|
||||
ExtendedStandardLibrary: true,
|
||||
ThirdPartyModules: true,
|
||||
ExportedSymbols: true,
|
||||
UnexportedSymbols: true,
|
||||
},
|
||||
},
|
||||
wantSymbols: []string{
|
||||
"encoding/json.Marshal",
|
||||
@ -273,7 +259,7 @@ func Test_machoGoSymbolCapture(t *testing.T) {
|
||||
f, err := macho.NewFile(reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
symbols := captureMachoGoSymbols(f, SymbolConfig{Go: tt.cfg})
|
||||
symbols := captureMachoGoSymbols(f, tt.cfg)
|
||||
symbolSet := make(map[string]struct{}, len(symbols))
|
||||
for _, sym := range symbols {
|
||||
symbolSet[sym] = struct{}{}
|
||||
@ -309,8 +295,8 @@ func Test_machoNMSymbols_goReturnsSymbols(t *testing.T) {
|
||||
{Name: "go", Version: "1.24", Kind: file.ToolchainKindCompiler},
|
||||
}
|
||||
cfg := SymbolConfig{
|
||||
Types: []string{"T", "t"},
|
||||
Go: GoSymbolConfig{
|
||||
Types: []string{"T", "t"},
|
||||
StandardLibrary: true,
|
||||
ExtendedStandardLibrary: true,
|
||||
ThirdPartyModules: true,
|
||||
|
||||
47
syft/file/cataloger/executable/symbols.go
Normal file
47
syft/file/cataloger/executable/symbols.go
Normal file
@ -0,0 +1,47 @@
|
||||
package executable
|
||||
|
||||
import "github.com/anchore/syft/syft/file"
|
||||
|
||||
// shouldCaptureSymbols determines whether symbols should be captured for the given executable
|
||||
// based on the configured capture scopes. If any configured scope matches the executable's
|
||||
// characteristics, symbols will be captured.
|
||||
func shouldCaptureSymbols(data *file.Executable, cfg SymbolConfig) bool {
|
||||
if data == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, scope := range cfg.CaptureScope {
|
||||
switch scope {
|
||||
case SymbolScopeNone:
|
||||
// explicit "none" means don't capture (but continue checking other scopes)
|
||||
continue
|
||||
case SymbolScopeAll:
|
||||
return true
|
||||
case SymbolScopeLibraries:
|
||||
if data.HasExports {
|
||||
return true
|
||||
}
|
||||
case SymbolScopeApplications:
|
||||
if data.HasEntrypoint {
|
||||
return true
|
||||
}
|
||||
case SymbolScopeGolang:
|
||||
if hasGolangToolchain(data) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if no scopes matched, do not capture symbols (empty scope means none)
|
||||
return false
|
||||
}
|
||||
|
||||
// hasGolangToolchain checks if the executable was built with the Go toolchain.
|
||||
func hasGolangToolchain(data *file.Executable) bool {
|
||||
for _, tc := range data.Toolchains {
|
||||
if tc.Name == "go" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
226
syft/file/cataloger/executable/symbols_test.go
Normal file
226
syft/file/cataloger/executable/symbols_test.go
Normal file
@ -0,0 +1,226 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
func TestShouldCaptureSymbols(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data *file.Executable
|
||||
cfg SymbolConfig
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "nil data returns false",
|
||||
data: nil,
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeAll},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "empty capture scope returns false",
|
||||
data: &file.Executable{},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "scope none returns false",
|
||||
data: &file.Executable{},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeNone},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "scope all returns true",
|
||||
data: &file.Executable{},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeAll},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "scope libraries with exports returns true",
|
||||
data: &file.Executable{
|
||||
HasExports: true,
|
||||
},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeLibraries},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "scope libraries without exports returns false",
|
||||
data: &file.Executable{
|
||||
HasExports: false,
|
||||
},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeLibraries},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "scope applications with entrypoint returns true",
|
||||
data: &file.Executable{
|
||||
HasEntrypoint: true,
|
||||
},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeApplications},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "scope applications without entrypoint returns false",
|
||||
data: &file.Executable{
|
||||
HasEntrypoint: false,
|
||||
},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeApplications},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "scope golang with go toolchain returns true",
|
||||
data: &file.Executable{
|
||||
Toolchains: []file.Toolchain{
|
||||
{Name: "go", Version: "1.21.0", Kind: file.ToolchainKindCompiler},
|
||||
},
|
||||
},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeGolang},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "scope golang without go toolchain returns false",
|
||||
data: &file.Executable{
|
||||
Toolchains: []file.Toolchain{
|
||||
{Name: "gcc", Version: "12.0.0", Kind: file.ToolchainKindCompiler},
|
||||
},
|
||||
},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeGolang},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "scope golang with empty toolchains returns false",
|
||||
data: &file.Executable{},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeGolang},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "multiple scopes with one match returns true",
|
||||
data: &file.Executable{
|
||||
HasExports: false,
|
||||
HasEntrypoint: true,
|
||||
},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeLibraries, SymbolScopeApplications},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "multiple scopes with no match returns false",
|
||||
data: &file.Executable{
|
||||
HasExports: false,
|
||||
HasEntrypoint: false,
|
||||
},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeLibraries, SymbolScopeApplications},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "none scope followed by matching scope returns true",
|
||||
data: &file.Executable{
|
||||
HasEntrypoint: true,
|
||||
},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeNone, SymbolScopeApplications},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "go toolchain among multiple toolchains returns true",
|
||||
data: &file.Executable{
|
||||
Toolchains: []file.Toolchain{
|
||||
{Name: "gcc", Version: "12.0.0", Kind: file.ToolchainKindCompiler},
|
||||
{Name: "go", Version: "1.21.0", Kind: file.ToolchainKindCompiler},
|
||||
},
|
||||
},
|
||||
cfg: SymbolConfig{
|
||||
CaptureScope: []SymbolCaptureScope{SymbolScopeGolang},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := shouldCaptureSymbols(tt.data, tt.cfg)
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasGolangToolchain(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data *file.Executable
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "empty toolchains",
|
||||
data: &file.Executable{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "no go toolchain",
|
||||
data: &file.Executable{
|
||||
Toolchains: []file.Toolchain{
|
||||
{Name: "gcc", Version: "12.0.0", Kind: file.ToolchainKindCompiler},
|
||||
{Name: "clang", Version: "15.0.0", Kind: file.ToolchainKindCompiler},
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "has go toolchain",
|
||||
data: &file.Executable{
|
||||
Toolchains: []file.Toolchain{
|
||||
{Name: "go", Version: "1.21.0", Kind: file.ToolchainKindCompiler},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "go toolchain among others",
|
||||
data: &file.Executable{
|
||||
Toolchains: []file.Toolchain{
|
||||
{Name: "gcc", Version: "12.0.0", Kind: file.ToolchainKindCompiler},
|
||||
{Name: "go", Version: "1.21.0", Kind: file.ToolchainKindCompiler},
|
||||
{Name: "ld", Version: "2.38", Kind: file.ToolchainKindLinker},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := hasGolangToolchain(tt.data)
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
3
syft/file/cataloger/executable/test-fixtures/toolchains/.gitignore
vendored
Normal file
3
syft/file/cataloger/executable/test-fixtures/toolchains/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
bin/
|
||||
Dockerfile.sha256
|
||||
*.fingerprint
|
||||
@ -0,0 +1,15 @@
|
||||
# invoke all make files in subdirectories
|
||||
.PHONY: all gcc clang
|
||||
|
||||
all: gcc clang
|
||||
|
||||
gcc:
|
||||
$(MAKE) -C gcc
|
||||
|
||||
clang:
|
||||
$(MAKE) -C clang
|
||||
|
||||
%:
|
||||
@for dir in gcc clang; do \
|
||||
$(MAKE) -C $$dir $@; \
|
||||
done
|
||||
@ -0,0 +1 @@
|
||||
FROM silkeh/clang:18.1.8
|
||||
@ -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
|
||||
@ -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
|
||||
@ -0,0 +1,6 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello, World!\n");
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
FROM gcc:13.4.0
|
||||
@ -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
|
||||
@ -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
|
||||
@ -0,0 +1,6 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello, World!\n");
|
||||
return 0;
|
||||
}
|
||||
81
syft/file/cataloger/executable/toolchains.go
Normal file
81
syft/file/cataloger/executable/toolchains.go
Normal file
@ -0,0 +1,81 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"debug/buildinfo"
|
||||
"debug/elf"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
var (
|
||||
clangVersionPattern = regexp.MustCompile(`clang version (\d+\.\d+\.\d+)`)
|
||||
gccVersionPattern = regexp.MustCompile(`GCC: \([^)]+\) (\d+\.\d+\.\d+)`)
|
||||
)
|
||||
|
||||
// elfGolangToolchainEvidence attempts to extract Go toolchain information from the ELF file.
|
||||
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,
|
||||
Kind: file.ToolchainKindCompiler,
|
||||
}
|
||||
}
|
||||
|
||||
// cToolchainEvidence attempts to extract C/C++ compiler information from the ELF .comment section.
|
||||
// This detects GCC and Clang compilers based on their version strings.
|
||||
func cToolchainEvidence(f *elf.File) *file.Toolchain {
|
||||
commentSection := f.Section(".comment")
|
||||
if commentSection == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := commentSection.Data()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// the .comment section contains null-terminated strings
|
||||
comments := strings.Split(string(data), "\x00")
|
||||
|
||||
// check for clang first since clang binaries often have both GCC and clang entries
|
||||
// (clang includes GCC compatibility info)
|
||||
for _, comment := range comments {
|
||||
if match := clangVersionPattern.FindStringSubmatch(comment); match != nil {
|
||||
return &file.Toolchain{
|
||||
Name: "clang",
|
||||
Version: match[1],
|
||||
Kind: file.ToolchainKindCompiler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if not clang, check for GCC
|
||||
for _, comment := range comments {
|
||||
if match := gccVersionPattern.FindStringSubmatch(comment); match != nil {
|
||||
return &file.Toolchain{
|
||||
Name: "gcc",
|
||||
Version: match[1],
|
||||
Kind: file.ToolchainKindCompiler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isGoToolchainPresent(toolchains []file.Toolchain) bool {
|
||||
for _, tc := range toolchains {
|
||||
if tc.Name == "go" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
61
syft/file/cataloger/executable/toolchains_test.go
Normal file
61
syft/file/cataloger/executable/toolchains_test.go
Normal file
@ -0,0 +1,61 @@
|
||||
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"
|
||||
)
|
||||
|
||||
func Test_cToolchainDetection(t *testing.T) {
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("test-fixtures/toolchains", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
want *file.Toolchain
|
||||
}{
|
||||
{
|
||||
name: "gcc binary",
|
||||
fixture: "gcc/bin/hello_gcc",
|
||||
want: &file.Toolchain{
|
||||
Name: "gcc",
|
||||
Version: "13.4.0",
|
||||
Kind: file.ToolchainKindCompiler,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "clang binary",
|
||||
fixture: "clang/bin/hello_clang",
|
||||
want: &file.Toolchain{
|
||||
Name: "clang",
|
||||
Version: "18.1.8",
|
||||
Kind: file.ToolchainKindCompiler,
|
||||
},
|
||||
},
|
||||
}
|
||||
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 := cToolchainEvidence(f)
|
||||
|
||||
if d := cmp.Diff(tt.want, got); d != "" {
|
||||
t.Errorf("cToolchainEvidence() mismatch (-want +got):\n%s", d)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,7 @@ type (
|
||||
// RelocationReadOnly indicates the RELRO security protection level applied to an ELF binary.
|
||||
RelocationReadOnly string
|
||||
|
||||
//SymbolType string
|
||||
// SymbolType string
|
||||
|
||||
ToolchainKind string
|
||||
)
|
||||
@ -26,11 +26,11 @@ const (
|
||||
RelocationReadOnlyFull RelocationReadOnly = "full" // full RELRO protection
|
||||
|
||||
//// from https://pkg.go.dev/cmd/nm
|
||||
//SymbolTypeText SymbolType = "T" // text (code) segment symbol
|
||||
//SymbolTypeTextStatic SymbolType = "t" // static text segment symbol
|
||||
//SymbolTypeReadOnly SymbolType = "R" // read-only data segment symbol
|
||||
//SymbolTypeReadOnlyStatic SymbolType = "r" // static read-only data segment symbol
|
||||
//SymbolTypeData SymbolType = "D" // data segment symbol
|
||||
// SymbolTypeText SymbolType = "T" // text (code) segment symbol
|
||||
// SymbolTypeTextStatic SymbolType = "t" // static text segment symbol
|
||||
// SymbolTypeReadOnly SymbolType = "R" // read-only data segment symbol
|
||||
// SymbolTypeReadOnlyStatic SymbolType = "r" // static read-only data segment symbol
|
||||
// SymbolTypeData SymbolType = "D" // data segment symbol
|
||||
//SymbolTypeDataStatic SymbolType = "d" // static data segment symbol
|
||||
//SymbolTypeBSS SymbolType = "B" // bss segment symbol
|
||||
//SymbolTypeBSSStatic SymbolType = "b" // static bss segment symbol
|
||||
@ -56,7 +56,7 @@ type Executable struct {
|
||||
ELFSecurityFeatures *ELFSecurityFeatures `json:"elfSecurityFeatures,omitempty" yaml:"elfSecurityFeatures" mapstructure:"elfSecurityFeatures"`
|
||||
|
||||
// Symbols captures the selection from the symbol table found in the binary.
|
||||
//Symbols []Symbol `json:"symbols,omitempty" yaml:"symbols" mapstructure:"symbols"`
|
||||
// Symbols []Symbol `json:"symbols,omitempty" yaml:"symbols" mapstructure:"symbols"`
|
||||
SymbolNames []string `json:"symbolNames,omitempty" yaml:"symbolNames" mapstructure:"symbolNames"`
|
||||
|
||||
// Toolchains captures information about the the compiler, linker, runtime, or other toolchains used to build (or otherwise exist within) the executable.
|
||||
@ -71,7 +71,7 @@ type Toolchain struct {
|
||||
// TODO: should we allow for aux information here? free form?
|
||||
}
|
||||
|
||||
//type Symbol struct {
|
||||
// type Symbol struct {
|
||||
// //Type SymbolType `json:"type" yaml:"type" mapstructure:"type"`
|
||||
// Type string `json:"type" yaml:"type" mapstructure:"type"`
|
||||
// Name string `json:"name" yaml:"name" mapstructure:"name"`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user