fix: don't panic on universal go binaries (#2078)

If crypto settings or arch cannot be determined, still attempt to catalog packages from
the build info, rather than panicking.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
This commit is contained in:
William Murphy 2023-08-30 08:37:50 -04:00 committed by GitHub
parent 2b7a9d0be3
commit cfebae27f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 70 additions and 91 deletions

View File

@ -16,7 +16,6 @@ import (
"golang.org/x/mod/module" "golang.org/x/mod/module"
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -57,11 +56,11 @@ func (c *goBinaryCataloger) parseGoBinary(resolver file.Resolver, _ *generic.Env
return nil, nil, err return nil, nil, err
} }
mods, archs := scanFile(unionReader, reader.RealPath) mods := scanFile(unionReader, reader.RealPath)
internal.CloseAndLogError(reader.ReadCloser, reader.RealPath) internal.CloseAndLogError(reader.ReadCloser, reader.RealPath)
for i, mod := range mods { for _, mod := range mods {
pkgs = append(pkgs, c.buildGoPkgInfo(resolver, reader.Location, mod, archs[i])...) pkgs = append(pkgs, c.buildGoPkgInfo(resolver, reader.Location, mod, mod.arch)...)
} }
return pkgs, nil, nil return pkgs, nil, nil
} }
@ -151,42 +150,6 @@ func extractVersionFromLDFlags(ldflags string) (majorVersion string, fullVersion
return "", "" return "", ""
} }
// getArchs finds a binary architecture by two ways:
// 1) reading build info from binaries compiled by go1.18+
// 2) reading file headers from binaries compiled by < go1.18
func getArchs(readers []io.ReaderAt, builds []*extendedBuildInfo) []string {
if len(readers) != len(builds) {
log.Trace("golang cataloger: bin parsing: number of builds and readers doesn't match")
return nil
}
if len(readers) == 0 || len(builds) == 0 {
log.Tracef("golang cataloger: bin parsing: %d readers and %d build info items", len(readers), len(builds))
return nil
}
archs := make([]string, len(builds))
for i, build := range builds {
archs[i] = getGOARCH(build.Settings)
}
// if architecture was found via build settings return
if archs[0] != "" {
return archs
}
for i, r := range readers {
a, err := getGOARCHFromBin(r)
if err != nil {
log.Tracef("golang cataloger: bin parsing: getting arch from binary: %v", err)
continue
}
archs[i] = a
}
return archs
}
func getGOARCH(settings []debug.BuildSetting) string { func getGOARCH(settings []debug.BuildSetting) string {
for _, s := range settings { for _, s := range settings {
if s.Key == GOARCH { if s.Key == GOARCH {

View File

@ -156,18 +156,12 @@ func TestBuildGoPkgInfo(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
mod *extendedBuildInfo mod *extendedBuildInfo
arch string
expected []pkg.Package expected []pkg.Package
}{ }{
{
name: "parse an empty mod",
mod: nil,
expected: []pkg.Package(nil),
},
{ {
name: "package without name", name: "package without name",
mod: &extendedBuildInfo{ mod: &extendedBuildInfo{
&debug.BuildInfo{ BuildInfo: &debug.BuildInfo{
Deps: []*debug.Module{ Deps: []*debug.Module{
{ {
Path: "github.com/adrg/xdg", Path: "github.com/adrg/xdg",
@ -177,7 +171,9 @@ func TestBuildGoPkgInfo(t *testing.T) {
Version: "v0.2.1", Version: "v0.2.1",
}, },
}, },
}, nil, },
cryptoSettings: nil,
arch: "",
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@ -200,14 +196,13 @@ func TestBuildGoPkgInfo(t *testing.T) {
}, },
{ {
name: "buildGoPkgInfo parses a blank mod and returns no packages", name: "buildGoPkgInfo parses a blank mod and returns no packages",
mod: &extendedBuildInfo{&debug.BuildInfo{}, nil}, mod: &extendedBuildInfo{&debug.BuildInfo{}, nil, ""},
expected: []pkg.Package(nil), expected: []pkg.Package(nil),
}, },
{ {
name: "parse a mod without main module", name: "parse a mod without main module",
arch: archDetails,
mod: &extendedBuildInfo{ mod: &extendedBuildInfo{
&debug.BuildInfo{ BuildInfo: &debug.BuildInfo{
GoVersion: goCompiledVersion, GoVersion: goCompiledVersion,
Settings: []debug.BuildSetting{ Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails}, {Key: "GOARCH", Value: archDetails},
@ -221,7 +216,9 @@ func TestBuildGoPkgInfo(t *testing.T) {
Sum: "h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic=", Sum: "h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic=",
}, },
}, },
}, nil, },
cryptoSettings: nil,
arch: archDetails,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@ -249,9 +246,8 @@ func TestBuildGoPkgInfo(t *testing.T) {
}, },
{ {
name: "parse a mod with path but no main module", name: "parse a mod with path but no main module",
arch: archDetails,
mod: &extendedBuildInfo{ mod: &extendedBuildInfo{
&debug.BuildInfo{ BuildInfo: &debug.BuildInfo{
GoVersion: goCompiledVersion, GoVersion: goCompiledVersion,
Settings: []debug.BuildSetting{ Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails}, {Key: "GOARCH", Value: archDetails},
@ -259,7 +255,9 @@ func TestBuildGoPkgInfo(t *testing.T) {
{Key: "GOAMD64", Value: "v1"}, {Key: "GOAMD64", Value: "v1"},
}, },
Path: "github.com/a/b/c", Path: "github.com/a/b/c",
}, []string{"boringcrypto + fips"}, },
cryptoSettings: []string{"boringcrypto + fips"},
arch: archDetails,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@ -294,9 +292,8 @@ func TestBuildGoPkgInfo(t *testing.T) {
}, },
{ {
name: "parse a mod without packages", name: "parse a mod without packages",
arch: archDetails,
mod: &extendedBuildInfo{ mod: &extendedBuildInfo{
&debug.BuildInfo{ BuildInfo: &debug.BuildInfo{
GoVersion: goCompiledVersion, GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{ Settings: []debug.BuildSetting{
@ -304,15 +301,16 @@ func TestBuildGoPkgInfo(t *testing.T) {
{Key: "GOOS", Value: "darwin"}, {Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"}, {Key: "GOAMD64", Value: "v1"},
}, },
}, nil, },
cryptoSettings: nil,
arch: archDetails,
}, },
expected: []pkg.Package{unmodifiedMain}, expected: []pkg.Package{unmodifiedMain},
}, },
{ {
name: "parse main mod and replace devel pseudo version and ldflags exists (but contains no version)", name: "parse main mod and replace devel pseudo version and ldflags exists (but contains no version)",
arch: archDetails,
mod: &extendedBuildInfo{ mod: &extendedBuildInfo{
&debug.BuildInfo{ BuildInfo: &debug.BuildInfo{
GoVersion: goCompiledVersion, GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{ Settings: []debug.BuildSetting{
@ -323,7 +321,9 @@ func TestBuildGoPkgInfo(t *testing.T) {
{Key: "vcs.time", Value: "2022-10-14T19:54:57Z"}, {Key: "vcs.time", Value: "2022-10-14T19:54:57Z"},
{Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X blah=foobar`}, {Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X blah=foobar`},
}, },
}, nil, },
cryptoSettings: nil,
arch: archDetails,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@ -359,9 +359,8 @@ func TestBuildGoPkgInfo(t *testing.T) {
}, },
{ {
name: "parse main mod and replace devel version with one from ldflags with vcs. build settings", name: "parse main mod and replace devel version with one from ldflags with vcs. build settings",
arch: archDetails,
mod: &extendedBuildInfo{ mod: &extendedBuildInfo{
&debug.BuildInfo{ BuildInfo: &debug.BuildInfo{
GoVersion: goCompiledVersion, GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{ Settings: []debug.BuildSetting{
@ -372,7 +371,9 @@ func TestBuildGoPkgInfo(t *testing.T) {
{Key: "vcs.time", Value: "2022-10-14T19:54:57Z"}, {Key: "vcs.time", Value: "2022-10-14T19:54:57Z"},
{Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`}, {Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`},
}, },
}, nil, },
cryptoSettings: nil,
arch: archDetails,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@ -408,9 +409,8 @@ func TestBuildGoPkgInfo(t *testing.T) {
}, },
{ {
name: "parse main mod and replace devel version with one from ldflags without any vcs. build settings", name: "parse main mod and replace devel version with one from ldflags without any vcs. build settings",
arch: archDetails,
mod: &extendedBuildInfo{ mod: &extendedBuildInfo{
&debug.BuildInfo{ BuildInfo: &debug.BuildInfo{
GoVersion: goCompiledVersion, GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{ Settings: []debug.BuildSetting{
@ -419,7 +419,9 @@ func TestBuildGoPkgInfo(t *testing.T) {
{Key: "GOAMD64", Value: "v1"}, {Key: "GOAMD64", Value: "v1"},
{Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`}, {Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`},
}, },
}, nil, },
cryptoSettings: nil,
arch: archDetails,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@ -453,9 +455,8 @@ func TestBuildGoPkgInfo(t *testing.T) {
}, },
{ {
name: "parse main mod and replace devel version with one from ldflags main.version without any vcs. build settings", name: "parse main mod and replace devel version with one from ldflags main.version without any vcs. build settings",
arch: archDetails,
mod: &extendedBuildInfo{ mod: &extendedBuildInfo{
&debug.BuildInfo{ BuildInfo: &debug.BuildInfo{
GoVersion: goCompiledVersion, GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{ Settings: []debug.BuildSetting{
@ -464,7 +465,9 @@ func TestBuildGoPkgInfo(t *testing.T) {
{Key: "GOAMD64", Value: "v1"}, {Key: "GOAMD64", Value: "v1"},
{Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X main.version=0.79.0`}, {Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X main.version=0.79.0`},
}, },
}, nil, },
cryptoSettings: nil,
arch: archDetails,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@ -498,9 +501,8 @@ func TestBuildGoPkgInfo(t *testing.T) {
}, },
{ {
name: "parse main mod and replace devel version with one from ldflags main.Version without any vcs. build settings", name: "parse main mod and replace devel version with one from ldflags main.Version without any vcs. build settings",
arch: archDetails,
mod: &extendedBuildInfo{ mod: &extendedBuildInfo{
&debug.BuildInfo{ BuildInfo: &debug.BuildInfo{
GoVersion: goCompiledVersion, GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{ Settings: []debug.BuildSetting{
@ -509,7 +511,9 @@ func TestBuildGoPkgInfo(t *testing.T) {
{Key: "GOAMD64", Value: "v1"}, {Key: "GOAMD64", Value: "v1"},
{Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X main.Version=0.79.0`}, {Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X main.Version=0.79.0`},
}, },
}, nil, },
cryptoSettings: nil,
arch: archDetails,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@ -543,9 +547,8 @@ func TestBuildGoPkgInfo(t *testing.T) {
}, },
{ {
name: "parse main mod and replace devel version with a pseudo version", name: "parse main mod and replace devel version with a pseudo version",
arch: archDetails,
mod: &extendedBuildInfo{ mod: &extendedBuildInfo{
&debug.BuildInfo{ BuildInfo: &debug.BuildInfo{
GoVersion: goCompiledVersion, GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{ Settings: []debug.BuildSetting{
@ -555,7 +558,9 @@ func TestBuildGoPkgInfo(t *testing.T) {
{Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"}, {Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"},
{Key: "vcs.time", Value: "2022-10-14T19:54:57Z"}, {Key: "vcs.time", Value: "2022-10-14T19:54:57Z"},
}, },
}, nil, },
cryptoSettings: nil,
arch: archDetails,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@ -590,9 +595,8 @@ func TestBuildGoPkgInfo(t *testing.T) {
}, },
{ {
name: "parse a populated mod string and returns packages but no source info", name: "parse a populated mod string and returns packages but no source info",
arch: archDetails,
mod: &extendedBuildInfo{ mod: &extendedBuildInfo{
&debug.BuildInfo{ BuildInfo: &debug.BuildInfo{
GoVersion: goCompiledVersion, GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{ Settings: []debug.BuildSetting{
@ -612,7 +616,9 @@ func TestBuildGoPkgInfo(t *testing.T) {
Sum: "h1:DYssiUV1pBmKqzKsm4mqXx8artqC0Q8HgZsVI3lMsAg=", Sum: "h1:DYssiUV1pBmKqzKsm4mqXx8artqC0Q8HgZsVI3lMsAg=",
}, },
}, },
}, nil, },
cryptoSettings: nil,
arch: archDetails,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@ -664,9 +670,8 @@ func TestBuildGoPkgInfo(t *testing.T) {
}, },
{ {
name: "parse a populated mod string and returns packages when a replace directive exists", name: "parse a populated mod string and returns packages when a replace directive exists",
arch: archDetails,
mod: &extendedBuildInfo{ mod: &extendedBuildInfo{
&debug.BuildInfo{ BuildInfo: &debug.BuildInfo{
GoVersion: goCompiledVersion, GoVersion: goCompiledVersion,
Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"},
Settings: []debug.BuildSetting{ Settings: []debug.BuildSetting{
@ -691,7 +696,9 @@ func TestBuildGoPkgInfo(t *testing.T) {
}, },
}, },
}, },
}, nil, },
cryptoSettings: nil,
arch: archDetails,
}, },
expected: []pkg.Package{ expected: []pkg.Package{
{ {
@ -756,7 +763,7 @@ func TestBuildGoPkgInfo(t *testing.T) {
) )
c := goBinaryCataloger{} c := goBinaryCataloger{}
pkgs := c.buildGoPkgInfo(fileresolver.Empty{}, location, test.mod, test.arch) pkgs := c.buildGoPkgInfo(fileresolver.Empty{}, location, test.mod, test.mod.arch)
assert.Equal(t, test.expected, pkgs) assert.Equal(t, test.expected, pkgs)
}) })
} }

View File

@ -15,16 +15,17 @@ import (
type extendedBuildInfo struct { type extendedBuildInfo struct {
*debug.BuildInfo *debug.BuildInfo
cryptoSettings []string cryptoSettings []string
arch string
} }
// scanFile scans file to try to report the Go and module versions. // scanFile scans file to try to report the Go and module versions.
func scanFile(reader unionreader.UnionReader, filename string) ([]*extendedBuildInfo, []string) { func scanFile(reader unionreader.UnionReader, filename string) []*extendedBuildInfo {
// NOTE: multiple readers are returned to cover universal binaries, which are files // NOTE: multiple readers are returned to cover universal binaries, which are files
// with more than one binary // with more than one binary
readers, err := unionreader.GetReaders(reader) readers, err := unionreader.GetReaders(reader)
if err != nil { if err != nil {
log.WithFields("error", err).Warnf("failed to open a golang binary") log.WithFields("error", err).Warnf("failed to open a golang binary")
return nil, nil return nil
} }
var builds []*extendedBuildInfo var builds []*extendedBuildInfo
@ -34,6 +35,7 @@ func scanFile(reader unionreader.UnionReader, filename string) ([]*extendedBuild
log.WithFields("file", filename, "error", err).Trace("unable to read golang buildinfo") log.WithFields("file", filename, "error", err).Trace("unable to read golang buildinfo")
continue continue
} }
// it's possible the reader just isn't a go binary, in which case just skip it
if bi == nil { if bi == nil {
continue continue
} }
@ -41,15 +43,22 @@ func scanFile(reader unionreader.UnionReader, filename string) ([]*extendedBuild
v, err := getCryptoInformation(r) v, err := getCryptoInformation(r)
if err != nil { if err != nil {
log.WithFields("file", filename, "error", err).Trace("unable to read golang version info") log.WithFields("file", filename, "error", err).Trace("unable to read golang version info")
continue // don't skip this build info.
// we can still catalog packages, even if we can't get the crypto information
}
arch := getGOARCH(bi.Settings)
if arch == "" {
arch, err = getGOARCHFromBin(r)
if err != nil {
log.WithFields("file", filename, "error", err).Trace("unable to read golang arch info")
// don't skip this build info.
// we can still catalog packages, even if we can't get the arch information
}
} }
builds = append(builds, &extendedBuildInfo{bi, v}) builds = append(builds, &extendedBuildInfo{bi, v, arch})
} }
return builds
archs := getArchs(readers, builds)
return builds, archs
} }
func getCryptoInformation(reader io.ReaderAt) ([]string, error) { func getCryptoInformation(reader io.ReaderAt) ([]string, error) {