mirror of
https://github.com/anchore/syft.git
synced 2026-11-02 15:36:41 +01:00
feat: optionally capture golang binary symbols (#4988)
--------- Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
co-authored by
Alex Goodman
parent
3252b6f806
commit
1dcac54b0e
@@ -13,6 +13,7 @@ import (
|
||||
"runtime/debug"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/mod/module"
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -49,15 +51,45 @@ const devel = "(devel)"
|
||||
type goBinaryCataloger struct {
|
||||
licenseResolver goLicenseResolver
|
||||
mainModuleVersion MainModuleVersionConfig
|
||||
symbolScope cataloging.SymbolScope
|
||||
|
||||
// stdlibSymbols holds the standard-library function symbols discovered per binary (keyed by the
|
||||
// binary's location), populated during parsing and consumed by stdlibProcessor when it builds the
|
||||
// synthetic "stdlib" package. Guarded by stdlibSymbolsMu because parsers run concurrently.
|
||||
stdlibSymbols map[file.Coordinates][]string
|
||||
stdlibSymbolsMu sync.Mutex
|
||||
}
|
||||
|
||||
func newGoBinaryCataloger(opts CatalogerConfig) *goBinaryCataloger {
|
||||
return &goBinaryCataloger{
|
||||
licenseResolver: newGoLicenseResolver(binaryCatalogerName, opts),
|
||||
mainModuleVersion: opts.MainModuleVersion,
|
||||
symbolScope: opts.CaptureSymbols,
|
||||
stdlibSymbols: make(map[file.Coordinates][]string),
|
||||
}
|
||||
}
|
||||
|
||||
// recordStdlibSymbols merges the standard-library symbols discovered for a binary location so the
|
||||
// stdlib processor can attach them to the synthetic stdlib package.
|
||||
func (c *goBinaryCataloger) recordStdlibSymbols(coord file.Coordinates, symbols []string) {
|
||||
if len(symbols) == 0 {
|
||||
return
|
||||
}
|
||||
c.stdlibSymbolsMu.Lock()
|
||||
defer c.stdlibSymbolsMu.Unlock()
|
||||
merged := slices.Concat(c.stdlibSymbols[coord], symbols)
|
||||
slices.Sort(merged)
|
||||
c.stdlibSymbols[coord] = slices.Compact(merged)
|
||||
}
|
||||
|
||||
// stdlibSymbolsFor returns the standard-library symbols recorded for a binary location. It returns a copy
|
||||
// so callers cannot alias (and later mutate or race on) the map's internal slice.
|
||||
func (c *goBinaryCataloger) stdlibSymbolsFor(coord file.Coordinates) []string {
|
||||
c.stdlibSymbolsMu.Lock()
|
||||
defer c.stdlibSymbolsMu.Unlock()
|
||||
return slices.Clone(c.stdlibSymbols[coord])
|
||||
}
|
||||
|
||||
// parseGoBinary catalogs packages found in the "buildinfo" section of a binary built by the go compiler.
|
||||
func (c *goBinaryCataloger) parseGoBinary(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var pkgs []pkg.Package
|
||||
@@ -68,7 +100,7 @@ func (c *goBinaryCataloger) parseGoBinary(ctx context.Context, resolver file.Res
|
||||
}
|
||||
defer internal.CloseAndLogError(reader.ReadCloser, reader.RealPath)
|
||||
|
||||
mods, errs := scanFile(reader.Location, unionReader)
|
||||
mods, errs := scanFile(reader.Location, unionReader, c.symbolScope != cataloging.SymbolScopeNone)
|
||||
|
||||
var rels []artifact.Relationship
|
||||
for _, mod := range mods {
|
||||
@@ -128,6 +160,15 @@ func (c *goBinaryCataloger) buildGoPkgInfo(ctx context.Context, resolver file.Re
|
||||
mod.Main = createMainModuleFromPath(mod)
|
||||
}
|
||||
|
||||
symbolsByModule, stdlibSymbols := moduleSymbols(mod.symbols, &mod.Main, mod.Deps)
|
||||
c.recordStdlibSymbols(location.Coordinates, stdlibSymbols)
|
||||
|
||||
if c.symbolScope != cataloging.SymbolScopeAll {
|
||||
// only the "all" scope attaches per-module symbols; for the "stdlib" scope we keep just the
|
||||
// recorded stdlib symbols. nil map lookups below then yield nil symbol lists for each module.
|
||||
symbolsByModule = nil
|
||||
}
|
||||
|
||||
var pkgs []pkg.Package
|
||||
for _, dep := range mod.Deps {
|
||||
if dep == nil {
|
||||
@@ -147,6 +188,7 @@ func (c *goBinaryCataloger) buildGoPkgInfo(ctx context.Context, resolver file.Re
|
||||
nil,
|
||||
mod.cryptoSettings,
|
||||
experiments,
|
||||
symbolsByModule[dep.Path],
|
||||
)
|
||||
|
||||
p := c.newGoBinaryPackage(
|
||||
@@ -164,7 +206,7 @@ func (c *goBinaryCataloger) buildGoPkgInfo(ctx context.Context, resolver file.Re
|
||||
return nil, pkgs
|
||||
}
|
||||
|
||||
main := c.makeGoMainPackage(ctx, resolver, mod, arch, location, reader)
|
||||
main := c.makeGoMainPackage(ctx, resolver, mod, arch, location, reader, symbolsByModule[mod.Main.Path])
|
||||
|
||||
return &main, pkgs
|
||||
}
|
||||
@@ -179,7 +221,7 @@ func missingMainModule(mod *extendedBuildInfo) bool {
|
||||
return mod.Main == moduleFromPartialPackageBuild
|
||||
}
|
||||
|
||||
func (c *goBinaryCataloger) makeGoMainPackage(ctx context.Context, resolver file.Resolver, mod *extendedBuildInfo, arch string, location file.Location, reader io.ReadSeekCloser) pkg.Package {
|
||||
func (c *goBinaryCataloger) makeGoMainPackage(ctx context.Context, resolver file.Resolver, mod *extendedBuildInfo, arch string, location file.Location, reader io.ReadSeekCloser, symbols []string) pkg.Package {
|
||||
gbs := getBuildSettings(mod.Settings)
|
||||
lics := c.licenseResolver.getLicenses(ctx, resolver, mod.Main.Path, mod.Main.Version)
|
||||
gover, experiments := getExperimentsFromVersion(mod.GoVersion)
|
||||
@@ -192,6 +234,7 @@ func (c *goBinaryCataloger) makeGoMainPackage(ctx context.Context, resolver file
|
||||
gbs,
|
||||
mod.cryptoSettings,
|
||||
experiments,
|
||||
symbols,
|
||||
)
|
||||
|
||||
if mod.Main.Version == devel {
|
||||
@@ -387,7 +430,7 @@ func getExperimentsFromVersion(version string) (string, []string) {
|
||||
version, rest, ok := strings.Cut(version, " ")
|
||||
if ok {
|
||||
// Assume they may add more non-version chunks in the future, so only look for "X:".
|
||||
for _, chunk := range strings.Split(rest, " ") {
|
||||
for chunk := range strings.SplitSeq(rest, " ") {
|
||||
if strings.HasPrefix(rest, "X:") {
|
||||
csv := strings.TrimPrefix(chunk, "X:")
|
||||
experiments = append(experiments, strings.Split(csv, ",")...)
|
||||
|
||||
Reference in New Issue
Block a user