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"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
@ -57,11 +56,11 @@ func (c *goBinaryCataloger) parseGoBinary(resolver file.Resolver, _ *generic.Env
return nil, nil, err
}
mods, archs := scanFile(unionReader, reader.RealPath)
mods := scanFile(unionReader, reader.RealPath)
internal.CloseAndLogError(reader.ReadCloser, reader.RealPath)
for i, mod := range mods {
pkgs = append(pkgs, c.buildGoPkgInfo(resolver, reader.Location, mod, archs[i])...)
for _, mod := range mods {
pkgs = append(pkgs, c.buildGoPkgInfo(resolver, reader.Location, mod, mod.arch)...)
}
return pkgs, nil, nil
}
@ -151,42 +150,6 @@ func extractVersionFromLDFlags(ldflags string) (majorVersion string, fullVersion
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 {
for _, s := range settings {
if s.Key == GOARCH {

View File

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

View File

@ -15,16 +15,17 @@ import (
type extendedBuildInfo struct {
*debug.BuildInfo
cryptoSettings []string
arch string
}
// 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
// with more than one binary
readers, err := unionreader.GetReaders(reader)
if err != nil {
log.WithFields("error", err).Warnf("failed to open a golang binary")
return nil, nil
return nil
}
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")
continue
}
// it's possible the reader just isn't a go binary, in which case just skip it
if bi == nil {
continue
}
@ -41,15 +43,22 @@ func scanFile(reader unionreader.UnionReader, filename string) ([]*extendedBuild
v, err := getCryptoInformation(r)
if err != nil {
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})
}
archs := getArchs(readers, builds)
return builds, archs
return builds
}
func getCryptoInformation(reader io.ReaderAt) ([]string, error) {