Skip to content

Commit e32215c

Browse files
feat(go): parse main module of go binary files (aquasecurity#6530)
Co-authored-by: Teppei Fukuda <[email protected]>
1 parent d4da83c commit e32215c

File tree

8 files changed

+105
-35
lines changed

8 files changed

+105
-35
lines changed

docs/docs/coverage/language/golang.md

+10
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,17 @@ $ trivy rootfs ./your_binary
7474
!!! note
7575
It doesn't work with UPX-compressed binaries.
7676

77+
#### Empty versions
78+
There are times when Go uses the `(devel)` version for modules/dependencies and Trivy can't resolve them:
79+
80+
- Only Go binaries installed using the `go install` command contain correct (semver) version for the main module.
81+
In other cases, Go uses the `(devel)` version[^3].
82+
- Dependencies replaced with local ones use the `(devel)` versions.
83+
84+
In these cases, the version of such packages is empty.
85+
7786
[^1]: It doesn't require the Internet access.
7887
[^2]: Need to download modules to local cache beforehand
88+
[^3]: See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477
7989

8090
[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies

pkg/dependency/parser/golang/binary/parse.go

+35-8
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package binary
22

33
import (
44
"debug/buildinfo"
5+
"sort"
56
"strings"
67

78
"golang.org/x/xerrors"
89

910
"github.com/aquasecurity/trivy/pkg/dependency/types"
11+
"github.com/aquasecurity/trivy/pkg/log"
1012
xio "github.com/aquasecurity/trivy/pkg/x/io"
1113
)
1214

@@ -29,10 +31,14 @@ func convertError(err error) error {
2931
return err
3032
}
3133

32-
type Parser struct{}
34+
type Parser struct {
35+
logger *log.Logger
36+
}
3337

3438
func NewParser() types.Parser {
35-
return &Parser{}
39+
return &Parser{
40+
logger: log.WithPrefix("gobinary"),
41+
}
3642
}
3743

3844
// Parse scans file to try to report the Go and module versions.
@@ -42,11 +48,22 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
4248
return nil, nil, convertError(err)
4349
}
4450

45-
libs := make([]types.Library, 0, len(info.Deps)+1)
46-
libs = append(libs, types.Library{
47-
Name: "stdlib",
48-
Version: strings.TrimPrefix(info.GoVersion, "go"),
49-
})
51+
libs := make([]types.Library, 0, len(info.Deps)+2)
52+
libs = append(libs, []types.Library{
53+
{
54+
// Add the Go version used to build this binary.
55+
Name: "stdlib",
56+
Version: strings.TrimPrefix(info.GoVersion, "go"),
57+
},
58+
{
59+
// Add main module
60+
Name: info.Main.Path,
61+
// Only binaries installed with `go install` contain semver version of the main module.
62+
// Other binaries use the `(devel)` version.
63+
// See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477.
64+
Version: p.checkVersion(info.Main.Path, info.Main.Version),
65+
},
66+
}...)
5067

5168
for _, dep := range info.Deps {
5269
// binaries with old go version may incorrectly add module in Deps
@@ -63,9 +80,19 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
6380

6481
libs = append(libs, types.Library{
6582
Name: mod.Path,
66-
Version: mod.Version,
83+
Version: p.checkVersion(mod.Path, mod.Version),
6784
})
6885
}
6986

87+
sort.Sort(types.Libraries(libs))
7088
return libs, nil, nil
7189
}
90+
91+
// checkVersion detects `(devel)` versions, removes them and adds a debug message about it.
92+
func (p *Parser) checkVersion(name, version string) string {
93+
if version == "(devel)" {
94+
p.logger.Debug("Unable to detect dependency version (`(devel)` is used). Version will be empty.", log.String("dependency", name))
95+
return ""
96+
}
97+
return version
98+
}

pkg/dependency/parser/golang/binary/parse_test.go

+46-16
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@ func TestParse(t *testing.T) {
2222
name: "ELF",
2323
inputFile: "testdata/test.elf",
2424
want: []types.Library{
25-
{
26-
Name: "stdlib",
27-
Version: "1.15.2",
28-
},
2925
{
3026
Name: "github.com/aquasecurity/go-pep440-version",
3127
Version: "v0.0.0-20210121094942-22b2f8951d46",
@@ -34,20 +30,24 @@ func TestParse(t *testing.T) {
3430
Name: "github.com/aquasecurity/go-version",
3531
Version: "v0.0.0-20210121072130-637058cfe492",
3632
},
33+
{
34+
Name: "github.com/aquasecurity/test",
35+
Version: "",
36+
},
3737
{
3838
Name: "golang.org/x/xerrors",
3939
Version: "v0.0.0-20200804184101-5ec99f83aff1",
4040
},
41+
{
42+
Name: "stdlib",
43+
Version: "1.15.2",
44+
},
4145
},
4246
},
4347
{
4448
name: "PE",
4549
inputFile: "testdata/test.exe",
4650
want: []types.Library{
47-
{
48-
Name: "stdlib",
49-
Version: "1.15.2",
50-
},
5151
{
5252
Name: "github.com/aquasecurity/go-pep440-version",
5353
Version: "v0.0.0-20210121094942-22b2f8951d46",
@@ -56,20 +56,24 @@ func TestParse(t *testing.T) {
5656
Name: "github.com/aquasecurity/go-version",
5757
Version: "v0.0.0-20210121072130-637058cfe492",
5858
},
59+
{
60+
Name: "github.com/aquasecurity/test",
61+
Version: "",
62+
},
5963
{
6064
Name: "golang.org/x/xerrors",
6165
Version: "v0.0.0-20200804184101-5ec99f83aff1",
6266
},
67+
{
68+
Name: "stdlib",
69+
Version: "1.15.2",
70+
},
6371
},
6472
},
6573
{
6674
name: "Mach-O",
6775
inputFile: "testdata/test.macho",
6876
want: []types.Library{
69-
{
70-
Name: "stdlib",
71-
Version: "1.15.2",
72-
},
7377
{
7478
Name: "github.com/aquasecurity/go-pep440-version",
7579
Version: "v0.0.0-20210121094942-22b2f8951d46",
@@ -78,28 +82,54 @@ func TestParse(t *testing.T) {
7882
Name: "github.com/aquasecurity/go-version",
7983
Version: "v0.0.0-20210121072130-637058cfe492",
8084
},
85+
{
86+
Name: "github.com/aquasecurity/test",
87+
Version: "",
88+
},
8189
{
8290
Name: "golang.org/x/xerrors",
8391
Version: "v0.0.0-20200804184101-5ec99f83aff1",
8492
},
93+
{
94+
Name: "stdlib",
95+
Version: "1.15.2",
96+
},
8597
},
8698
},
8799
{
88100
name: "with replace directive",
89101
inputFile: "testdata/replace.elf",
90102
want: []types.Library{
91-
{
92-
Name: "stdlib",
93-
Version: "1.16.4",
94-
},
95103
{
96104
Name: "github.com/davecgh/go-spew",
97105
Version: "v1.1.1",
98106
},
107+
{
108+
Name: "github.com/ebati/trivy-mod-parse",
109+
Version: "",
110+
},
99111
{
100112
Name: "github.com/go-sql-driver/mysql",
101113
Version: "v1.5.0",
102114
},
115+
{
116+
Name: "stdlib",
117+
Version: "1.16.4",
118+
},
119+
},
120+
},
121+
{
122+
name: "with semver main module version",
123+
inputFile: "testdata/semver-main-module-version.macho",
124+
want: []types.Library{
125+
{
126+
Name: "go.etcd.io/bbolt",
127+
Version: "v1.3.5",
128+
},
129+
{
130+
Name: "stdlib",
131+
Version: "1.20.6",
132+
},
103133
},
104134
},
105135
{
Binary file not shown.

pkg/fanal/analyzer/language/golang/binary/binary_test.go

+8-4
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,6 @@ func Test_gobinaryLibraryAnalyzer_Analyze(t *testing.T) {
2929
Type: types.GoBinary,
3030
FilePath: "testdata/executable_gobinary",
3131
Libraries: types.Packages{
32-
{
33-
Name: "stdlib",
34-
Version: "1.15.2",
35-
},
3632
{
3733
Name: "github.com/aquasecurity/go-pep440-version",
3834
Version: "v0.0.0-20210121094942-22b2f8951d46",
@@ -41,10 +37,18 @@ func Test_gobinaryLibraryAnalyzer_Analyze(t *testing.T) {
4137
Name: "github.com/aquasecurity/go-version",
4238
Version: "v0.0.0-20210121072130-637058cfe492",
4339
},
40+
{
41+
Name: "github.com/aquasecurity/test",
42+
Version: "",
43+
},
4444
{
4545
Name: "golang.org/x/xerrors",
4646
Version: "v0.0.0-20200804184101-5ec99f83aff1",
4747
},
48+
{
49+
Name: "stdlib",
50+
Version: "1.15.2",
51+
},
4852
},
4953
},
5054
},

pkg/purl/purl_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func TestNewPackageURL(t *testing.T) {
164164
typ: ftypes.GoModule,
165165
pkg: ftypes.Package{
166166
Name: "./private_repos/cnrm.googlesource.com/cnrm/",
167-
Version: "(devel)",
167+
Version: "",
168168
},
169169
want: nil,
170170
},

pkg/sbom/cyclonedx/marshal_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ func TestMarshaler_MarshalReport(t *testing.T) {
246246
// dependency has been replaced with local directory
247247
{
248248
Name: "./api",
249-
Version: "(devel)",
249+
Version: "",
250250
},
251251
},
252252
},
@@ -423,7 +423,7 @@ func TestMarshaler_MarshalReport(t *testing.T) {
423423
BOMRef: "3ff14136-e09f-4df9-80ea-000000000013",
424424
Type: cdx.ComponentTypeLibrary,
425425
Name: "./api",
426-
Version: "(devel)",
426+
Version: "",
427427
Properties: &[]cdx.Property{
428428
{
429429
Name: "aquasecurity:trivy:PkgType",

pkg/sbom/spdx/marshal_test.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -1056,7 +1056,7 @@ func TestMarshaler_Marshal(t *testing.T) {
10561056
Packages: []ftypes.Package{
10571057
{
10581058
Name: "./private_repos/cnrm.googlesource.com/cnrm/",
1059-
Version: "(devel)",
1059+
Version: "",
10601060
},
10611061
{
10621062
Name: "golang.org/x/crypto",
@@ -1105,10 +1105,9 @@ func TestMarshaler_Marshal(t *testing.T) {
11051105
},
11061106
},
11071107
{
1108-
PackageSPDXIdentifier: spdx.ElementID("Package-9a16e221e11f8a90"),
1108+
PackageSPDXIdentifier: spdx.ElementID("Package-b1c3b9e2363f5ff7"),
11091109
PackageDownloadLocation: "NONE",
11101110
PackageName: "./private_repos/cnrm.googlesource.com/cnrm/",
1111-
PackageVersion: "(devel)",
11121111
PackageLicenseConcluded: "NONE",
11131112
PackageLicenseDeclared: "NONE",
11141113
PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
@@ -1152,7 +1151,7 @@ func TestMarshaler_Marshal(t *testing.T) {
11521151
Relationships: []*spdx.Relationship{
11531152
{
11541153
RefA: spdx.DocElementID{ElementRefID: "Application-aab0f4e8cf174c67"},
1155-
RefB: spdx.DocElementID{ElementRefID: "Package-9a16e221e11f8a90"},
1154+
RefB: spdx.DocElementID{ElementRefID: "Package-b1c3b9e2363f5ff7"},
11561155
Relationship: "CONTAINS",
11571156
},
11581157
{

0 commit comments

Comments
 (0)