Skip to content

Commit

Permalink
feat(go): construct dependencies in the parser (#7973)
Browse files Browse the repository at this point in the history
Signed-off-by: knqyf263 <[email protected]>
  • Loading branch information
knqyf263 authored Nov 21, 2024
1 parent e0f2054 commit bcdc0bb
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 32 deletions.
67 changes: 47 additions & 20 deletions pkg/dependency/parser/golang/binary/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import (
"sort"
"strings"

"github.com/samber/lo"
"github.com/spf13/pflag"
"golang.org/x/mod/semver"
"golang.org/x/xerrors"

"github.com/aquasecurity/trivy/pkg/dependency"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
Expand Down Expand Up @@ -64,27 +66,12 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc
pkgs := make(ftypes.Packages, 0, len(info.Deps)+2)
pkgs = append(pkgs, ftypes.Package{
// Add the Go version used to build this binary.
ID: dependency.ID(ftypes.GoBinary, "stdlib", stdlibVersion),
Name: "stdlib",
Version: stdlibVersion,
Relationship: ftypes.RelationshipDirect, // Considered a direct dependency as the main module depends on the standard packages.
})

// There are times when gobinaries don't contain Main information.
// e.g. `Go` binaries (e.g. `go`, `gofmt`, etc.)
if info.Main.Path != "" {
pkgs = append(pkgs, ftypes.Package{
// Add main module
Name: info.Main.Path,
// Only binaries installed with `go install` contain semver version of the main module.
// Other binaries use the `(devel)` version, but still may contain a stamped version
// set via `go build -ldflags='-X main.version=<semver>'`, so we fallback to this as.
// as a secondary source.
// See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477.
Version: cmp.Or(p.checkVersion(info.Main.Path, info.Main.Version), p.ParseLDFlags(info.Main.Path, ldflags)),
Relationship: ftypes.RelationshipRoot,
})
}

for _, dep := range info.Deps {
// binaries with old go version may incorrectly add module in Deps
// In this case Path == "", Version == "Devel"
Expand All @@ -98,14 +85,49 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc
mod = dep.Replace
}

version := p.checkVersion(mod.Path, mod.Version)
pkgs = append(pkgs, ftypes.Package{
Name: mod.Path,
Version: p.checkVersion(mod.Path, mod.Version),
ID: dependency.ID(ftypes.GoBinary, mod.Path, version),
Name: mod.Path,
Version: version,
Relationship: ftypes.RelationshipUnknown,
})
}

// There are times when gobinaries don't contain Main information.
// e.g. `Go` binaries (e.g. `go`, `gofmt`, etc.)
var deps []ftypes.Dependency
if info.Main.Path != "" {
// Only binaries installed with `go install` contain semver version of the main module.
// Other binaries use the `(devel)` version, but still may contain a stamped version
// set via `go build -ldflags='-X main.version=<semver>'`, so we fallback to this as.
// as a secondary source.
// See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477.
version := cmp.Or(p.checkVersion(info.Main.Path, info.Main.Version), p.ParseLDFlags(info.Main.Path, ldflags))
root := ftypes.Package{
ID: dependency.ID(ftypes.GoBinary, info.Main.Path, version),
Name: info.Main.Path,
Version: version,
Relationship: ftypes.RelationshipRoot,
}

depIDs := lo.Map(pkgs, func(pkg ftypes.Package, _ int) string {
return pkg.ID
})
sort.Strings(depIDs)

deps = []ftypes.Dependency{
{
ID: root.ID,
DependsOn: depIDs, // Consider all packages as dependencies of the main module.
},
}
// Add main module
pkgs = append(pkgs, root)
}

sort.Sort(pkgs)
return pkgs, nil, nil
return pkgs, deps, nil
}

// checkVersion detects `(devel)` versions, removes them and adds a debug message about it.
Expand Down Expand Up @@ -153,7 +175,12 @@ func (p *Parser) ParseLDFlags(name string, flags []string) string {
// [1]: Versions that use prefixes from `defaultPrefixes`
// [2]: Other versions
var foundVersions = make([][]string, 3)
defaultPrefixes := []string{"main", "common", "version", "cmd"}
defaultPrefixes := []string{
"main",
"common",
"version",
"cmd",
}
for key, val := range x {
// It's valid to set the -X flags with quotes so we trim any that might
// have been provided: Ex:
Expand Down
79 changes: 67 additions & 12 deletions pkg/dependency/parser/golang/binary/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,111 +14,166 @@ import (
func TestParse(t *testing.T) {
wantPkgs := []ftypes.Package{
{
ID: "github.com/aquasecurity/test",
Name: "github.com/aquasecurity/test",
Version: "",
Relationship: ftypes.RelationshipRoot,
},
{
ID: "[email protected]",
Name: "stdlib",
Version: "v1.15.2",
Relationship: ftypes.RelationshipDirect,
},
{
ID: "github.com/aquasecurity/[email protected]",
Name: "github.com/aquasecurity/go-pep440-version",
Version: "v0.0.0-20210121094942-22b2f8951d46",
},
{
ID: "github.com/aquasecurity/[email protected]",
Name: "github.com/aquasecurity/go-version",
Version: "v0.0.0-20210121072130-637058cfe492",
},
{
ID: "golang.org/x/[email protected]",
Name: "golang.org/x/xerrors",
Version: "v0.0.0-20200804184101-5ec99f83aff1",
},
}
wantDeps := []ftypes.Dependency{
{
ID: "github.com/aquasecurity/test",
DependsOn: []string{
"github.com/aquasecurity/[email protected]",
"github.com/aquasecurity/[email protected]",
"golang.org/x/[email protected]",
"[email protected]",
},
},
}

tests := []struct {
name string
inputFile string
want []ftypes.Package
wantPkgs []ftypes.Package
wantDeps []ftypes.Dependency
wantErr string
}{
{
name: "ELF",
inputFile: "testdata/test.elf",
want: wantPkgs,
wantPkgs: wantPkgs,
wantDeps: wantDeps,
},
{
name: "PE",
inputFile: "testdata/test.exe",
want: wantPkgs,
wantPkgs: wantPkgs,
wantDeps: wantDeps,
},
{
name: "Mach-O",
inputFile: "testdata/test.macho",
want: wantPkgs,
wantPkgs: wantPkgs,
wantDeps: wantDeps,
},
{
name: "with replace directive",
inputFile: "testdata/replace.elf",
want: []ftypes.Package{
wantPkgs: []ftypes.Package{
{
ID: "github.com/ebati/trivy-mod-parse",
Name: "github.com/ebati/trivy-mod-parse",
Version: "",
Relationship: ftypes.RelationshipRoot,
},
{
ID: "[email protected]",
Name: "stdlib",
Version: "v1.16.4",
Relationship: ftypes.RelationshipDirect,
},
{
ID: "github.com/davecgh/[email protected]",
Name: "github.com/davecgh/go-spew",
Version: "v1.1.1",
},
{
ID: "github.com/go-sql-driver/[email protected]",
Name: "github.com/go-sql-driver/mysql",
Version: "v1.5.0",
},
},
wantDeps: []ftypes.Dependency{
{
ID: "github.com/ebati/trivy-mod-parse",
DependsOn: []string{
"github.com/davecgh/[email protected]",
"github.com/go-sql-driver/[email protected]",
"[email protected]",
},
},
},
},
{
name: "with semver main module version",
inputFile: "testdata/semver-main-module-version.macho",
want: []ftypes.Package{
wantPkgs: []ftypes.Package{
{
ID: "go.etcd.io/[email protected]",
Name: "go.etcd.io/bbolt",
Version: "v1.3.5",
Relationship: ftypes.RelationshipRoot,
},
{
ID: "[email protected]",
Name: "stdlib",
Version: "v1.20.6",
Relationship: ftypes.RelationshipDirect,
},
},
wantDeps: []ftypes.Dependency{
{
ID: "go.etcd.io/[email protected]",
DependsOn: []string{
"[email protected]",
},
},
},
},
{
name: "with -ldflags=\"-X main.version=v1.0.0\"",
inputFile: "testdata/main-version-via-ldflags.elf",
want: []ftypes.Package{
wantPkgs: []ftypes.Package{
{
ID: "github.com/aquasecurity/[email protected]",
Name: "github.com/aquasecurity/test",
Version: "v1.0.0",
Relationship: ftypes.RelationshipRoot,
},
{
ID: "[email protected]",
Name: "stdlib",
Version: "v1.22.1",
Relationship: ftypes.RelationshipDirect,
},
},
wantDeps: []ftypes.Dependency{
{
ID: "github.com/aquasecurity/[email protected]",
DependsOn: []string{
"[email protected]",
},
},
},
},
{
name: "goexperiment",
inputFile: "testdata/goexperiment",
want: []ftypes.Package{
wantPkgs: []ftypes.Package{
{
ID: "[email protected]",
Name: "stdlib",
Version: "v1.22.1",
Relationship: ftypes.RelationshipDirect,
Expand All @@ -137,15 +192,15 @@ func TestParse(t *testing.T) {
require.NoError(t, err)
defer f.Close()

got, _, err := binary.NewParser().Parse(f)
gotPkgs, gotDeps, err := binary.NewParser().Parse(f)
if tt.wantErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
assert.ErrorContains(t, err, tt.wantErr)
return
}

require.NoError(t, err)
assert.Equal(t, tt.want, got)
assert.Equal(t, tt.wantPkgs, gotPkgs)
assert.Equal(t, tt.wantDeps, gotDeps)
})
}
}
Expand Down
11 changes: 11 additions & 0 deletions pkg/fanal/analyzer/language/golang/binary/binary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,35 @@ func Test_gobinaryLibraryAnalyzer_Analyze(t *testing.T) {
FilePath: "testdata/executable_gobinary",
Packages: types.Packages{
{
ID: "github.com/aquasecurity/test",
Name: "github.com/aquasecurity/test",
Version: "",
Relationship: types.RelationshipRoot,
DependsOn: []string{
"github.com/aquasecurity/[email protected]",
"github.com/aquasecurity/[email protected]",
"golang.org/x/[email protected]",
"[email protected]",
},
},
{
ID: "[email protected]",
Name: "stdlib",
Version: "v1.15.2",
Relationship: types.RelationshipDirect,
},
{
ID: "github.com/aquasecurity/[email protected]",
Name: "github.com/aquasecurity/go-pep440-version",
Version: "v0.0.0-20210121094942-22b2f8951d46",
},
{
ID: "github.com/aquasecurity/[email protected]",
Name: "github.com/aquasecurity/go-version",
Version: "v0.0.0-20210121072130-637058cfe492",
},
{
ID: "golang.org/x/[email protected]",
Name: "golang.org/x/xerrors",
Version: "v0.0.0-20200804184101-5ec99f83aff1",
},
Expand Down

0 comments on commit bcdc0bb

Please sign in to comment.