Skip to content

Commit 8fb35e0

Browse files
committed
internal/scan: add binary extract mode
The extract mode spits out a json blob representing the minimal representation of a Go binary needed for govulncheck vulnerability detection. binary mode accepts both a Go binary and this representation as an input. The contents of extract should be regarded as a blob. The users of this flag should not rely on its representation. It might change in the future. Change-Id: I81027062d34609fed7541ad2092d4cbe5df0d118 Reviewed-on: https://go-review.googlesource.com/c/vuln/+/542035 Run-TryBot: Zvonimir Pavlinovic <zpavlinovic@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Ian Cottrell <iancottrell@google.com> Reviewed-by: Maceo Thompson <maceothompson@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
1 parent 3072335 commit 8fb35e0

20 files changed

+285
-23
lines changed

cmd/govulncheck/doc.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ with the -mode=binary flag:
5252
Govulncheck uses the binary's symbol information to find mentions of vulnerable
5353
functions. Its output omits call stacks, which require source code analysis.
5454
55+
Govulncheck also supports -mode=extract on a Go binary for extraction of minimal
56+
information needed to analyze the binary. This will produce a blob, typically much
57+
smaller than the binary, that can also be passed to govulncheck as an argument with
58+
-mode=binary. The users should not rely on the contents or representation of the blob.
59+
5560
Govulncheck exits successfully (exit code 0) if there are no vulnerabilities,
5661
and exits unsuccessfully if there are. It also exits successfully if the -json flag
5762
is provided, regardless of the number of detected vulnerabilities.

cmd/govulncheck/main_test.go

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"unsafe"
2525

2626
"github.com/google/go-cmdtest"
27+
"github.com/google/go-cmp/cmp"
2728
"golang.org/x/vuln/internal/govulncheck"
2829
"golang.org/x/vuln/internal/test"
2930
"golang.org/x/vuln/internal/web"
@@ -153,7 +154,10 @@ func TestCommand(t *testing.T) {
153154
varName := filepath.Base(md) + "_binary"
154155
os.Setenv(varName, binary)
155156
}
156-
runTestSuite(t, filepath.Join(testDir, "testdata", "testfiles"), govulndbURI.String(), *update)
157+
testFilesDir := filepath.Join(testDir, "testdata", "testfiles")
158+
os.Setenv("testdir", testFilesDir)
159+
160+
runTestSuite(t, testFilesDir, govulndbURI.String(), *update)
157161
if runtime.GOOS != "darwin" {
158162
// Binaries are not stripped on darwin with go1.21 and earlier. See #61051.
159163
runTestSuite(t, filepath.Join(testDir, "testdata", "strip"), govulndbURI.String(), *update)
@@ -196,7 +200,7 @@ func runTestSuite(t *testing.T, dir string, govulndb string, update bool) {
196200
}
197201
ts.DisableLogging = true
198202

199-
ts.Commands["govulncheck"] = func(args []string, inputFile string) ([]byte, error) {
203+
govulncheckCmd := func(args []string, inputFile string) ([]byte, error) {
200204
parallelLimiter <- struct{}{}
201205
defer func() { <-parallelLimiter }()
202206

@@ -250,6 +254,37 @@ func runTestSuite(t *testing.T, dir string, govulndb string, update bool) {
250254
}
251255
return out, err
252256
}
257+
ts.Commands["govulncheck"] = govulncheckCmd
258+
259+
// govulncheck-cmp is like govulncheck except that the last argument is a file
260+
// whose contents are compared to the output of govulncheck. This command does
261+
// not output anything.
262+
ts.Commands["govulncheck-cmp"] = func(args []string, inputFile string) ([]byte, error) {
263+
l := len(args)
264+
if l == 0 {
265+
return nil, nil
266+
}
267+
cmpArg := args[l-1]
268+
gArgs := args[:l-1]
269+
270+
out, err := govulncheckCmd(gArgs, inputFile)
271+
if err != nil {
272+
return nil, &cmdtest.ExitCodeErr{Msg: err.Error(), Code: 1}
273+
}
274+
got := string(out)
275+
276+
file, err := os.ReadFile(cmpArg)
277+
if err != nil {
278+
return nil, &cmdtest.ExitCodeErr{Msg: err.Error(), Code: 1}
279+
}
280+
want := string(file)
281+
282+
if diff := cmp.Diff(want, got); diff != "" {
283+
return nil, &cmdtest.ExitCodeErr{Msg: "govulncheck output not matching the file contents:\n" + diff, Code: 1}
284+
}
285+
return nil, nil
286+
}
287+
253288
if update {
254289
ts.Run(t, true)
255290
return
Binary file not shown.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#####
2+
# Test binary mode using the extracted binary blob.
3+
$ govulncheck -mode=binary ${testdir}/extract/vuln.blob --> FAIL 3
4+
Scanning your binary for known vulnerabilities...
5+
6+
Vulnerability #1: GO-2021-0265
7+
A maliciously crafted path can cause Get and other query functions to
8+
consume excessive amounts of CPU and time.
9+
More info: https://pkg.go.dev/vuln/GO-2021-0265
10+
Module: github.com/tidwall/gjson
11+
Found in: github.com/tidwall/gjson@v1.6.5
12+
Fixed in: github.com/tidwall/gjson@v1.9.3
13+
Example traces found:
14+
#1: gjson.Get
15+
#2: gjson.Result.Get
16+
17+
Vulnerability #2: GO-2021-0113
18+
Due to improper index calculation, an incorrectly formatted language tag can
19+
cause Parse to panic via an out of bounds read. If Parse is used to process
20+
untrusted user inputs, this may be used as a vector for a denial of service
21+
attack.
22+
More info: https://pkg.go.dev/vuln/GO-2021-0113
23+
Module: golang.org/x/text
24+
Found in: golang.org/x/text@v0.3.0
25+
Fixed in: golang.org/x/text@v0.3.7
26+
Example traces found:
27+
#1: language.Parse
28+
29+
Vulnerability #3: GO-2021-0054
30+
Due to improper bounds checking, maliciously crafted JSON objects can cause
31+
an out-of-bounds panic. If parsing user input, this may be used as a denial
32+
of service vector.
33+
More info: https://pkg.go.dev/vuln/GO-2021-0054
34+
Module: github.com/tidwall/gjson
35+
Found in: github.com/tidwall/gjson@v1.6.5
36+
Fixed in: github.com/tidwall/gjson@v1.6.6
37+
Example traces found:
38+
#1: gjson.Result.ForEach
39+
40+
Your code is affected by 3 vulnerabilities from 2 modules.
41+
42+
Share feedback at https://go.dev/s/govulncheck-feedback.
43+
44+
# Test extract mode. Due to the size of the blob even for smallest programs, we
45+
# directly compare its output to a target vuln_blob.json file.
46+
$ govulncheck-cmp -mode=extract ${moddir}/vuln/vuln_dont_run_me ${testdir}/extract/vuln.blob

cmd/govulncheck/testdata/testfiles/extract/vuln.blob

Lines changed: 2 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"name":"govulncheck-extract","version":"0.1.0"}{"modules":[]}{"name":"govulncheck-extract","version":"0.1.0"}

cmd/govulncheck/testdata/testfiles/failures/binary_fail.ct

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,54 @@ $ govulncheck -mode=binary notafile --> FAIL 2
44
"notafile" is not a file
55

66
#####
7-
# Test of passing a non-binary file to -mode=binary
7+
# Test of passing a non-binary and non-blob file to -mode=binary
88
$ govulncheck -mode=binary ${moddir}/vuln/go.mod --> FAIL 1
9-
govulncheck: could not parse provided binary: unrecognized file format
9+
govulncheck: unrecognized binary format
10+
11+
#####
12+
# Test of passing a blob with invalid header id
13+
$ govulncheck -mode=binary ${testdir}/failures/invalid_header_name.blob --> FAIL 1
14+
govulncheck: unrecognized binary format
15+
16+
#####
17+
# Test of passing a blob with invalid header version
18+
$ govulncheck -mode=binary ${testdir}/failures/invalid_header_version.blob --> FAIL 1
19+
govulncheck: unrecognized binary format
20+
21+
#####
22+
# Test of passing a blob with no header
23+
$ govulncheck -mode=binary ${testdir}/failures/no_header.blob --> FAIL 1
24+
govulncheck: unrecognized binary format
25+
26+
#####
27+
# Test of passing a blob with invalid header, i.e., no header
28+
$ govulncheck -mode=binary ${testdir}/failures/no_header.blob --> FAIL 1
29+
govulncheck: unrecognized binary format
30+
31+
#####
32+
# Test of passing a blob with no body
33+
$ govulncheck -mode=binary ${testdir}/failures/no_body.blob --> FAIL 1
34+
govulncheck: unrecognized binary format
35+
36+
#####
37+
# Test of passing an empty blob/file
38+
$ govulncheck -mode=binary ${testdir}/failures/empty.blob --> FAIL 1
39+
govulncheck: unrecognized binary format
40+
41+
#####
42+
# Test of passing an empty blob message
43+
$ govulncheck -mode=binary ${testdir}/failures/empty_message.blob --> FAIL 1
44+
govulncheck: unrecognized binary format
45+
46+
#####
47+
# Test of passing blob message with multiple headers
48+
$ govulncheck -mode=binary ${testdir}/failures/multi_header.blob --> FAIL 1
49+
govulncheck: unrecognized binary format
50+
51+
#####
52+
# Test of passing blob message with something after the body
53+
$ govulncheck -mode=binary ${testdir}/failures/multi_header.blob --> FAIL 1
54+
govulncheck: unrecognized binary format
1055

1156
#####
1257
# Test of trying to analyze multiple binaries

cmd/govulncheck/testdata/testfiles/failures/empty.blob

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#####
2+
# Test extraction of an unsupported file format
3+
$ govulncheck -mode=extract ${moddir}/vuln/go.mod --> FAIL 1
4+
govulncheck: unrecognized binary format

0 commit comments

Comments
 (0)