Vulnerability Scanning in Go With Govulncheck

Semaphore
7 min readFeb 16, 2023

Go recently introduced an official vulnerability scanner that promises to best every other third-party tool out there: govulncheck. This is a tool that understands which modules and standard library functions your Go project is using and gives warnings about known vulnerabilities that can affect the application.

In this article, I want to explore how it works and how we can combine it with CI/CD to secure our Go projects.

The state of Go’s security

As of September 2022, the state of security scanning in Go was disappointing. All we had were a few third-party, ruleset-based scanners like gosec or Snyk code checker. What was missing was a tool with access to a Go-specific known vulnerability database.

Enter govulnchecker

The Go security team introduced govulncheck in September 2022. Govulncheck is an open-source command line utility that can analyze code and give warnings about known issues in Go modules or its standard library. Behind the scenes, govulncheck grabs its data from the Go vulnerability database, which is maintained and curated by the Go security team.

Compared to other security tools, govulncheck has a few important advantages:

  • Smart: the tool warns you only if you actually use vulnerable code. This means it’s a lot less noisy than the likes of npm audit, which only scans the package manifest.
  • Comprehensive: the database feeds from multiple sources, including internal reports, package maintainer submissions, the National Vulnerability Database(NVD), and the GitHub Advisory Database.
  • Official: the Go developer team maintains the tool. And it will eventually find its way into the Go distribution itself.
  • Curated: the database is curated by the Go security team, implying a higher level of supervision.

Getting started with govulncheck

Getting started with govulncheck is as straightforward as installing the CLI with go install and running it in the project folder:

$ go install golang.org/x/vuln/cmd/govulncheck@latest
$ govulncheck ./...

Check your PATH if you get a “command not found” error. Go installs binaries in $GOPATH/bin. So, you may need to update your environment:

$ export PATH=$PATH:$HOME/go/bin
# or
$ export PATH=$PATH:$GOPATH/bin

Govulncheck is still experimental, so it has only a few options. We can supply -test to scan test files and -json for JSON output, and that’s it.

To see govulncheck in action, let’s fork and clone the semaphoreci-demo/semaphore-demo-go repository. As you can see, I’m using Go version 1.19 here:

$ go version
go version go1.19 linux/amd64
$ govulncheck ./...
govulncheck is an experimental tool. Share feedback at https://go.dev/s/govulncheck-feedback.
Scanning for dependencies with known vulnerabilities...
Found 3 known vulnerabilities.
Vulnerability #1: GO-2022-1144
An attacker can cause excessive memory growth in a Go server
accepting HTTP/2 requests. HTTP/2 server connections contain a
cache of HTTP header keys sent by the client. While the total
number of entries in this cache is capped, an attacker sending
very large keys can cause the server to allocate approximately
64 MiB per open connection.
Call stacks in your code:
main.go:76:28: github.com/semaphoreci-demos/semaphore-demo-go.main calls net/http.ListenAndServe
Found in: net/http@go1.19
Fixed in: net/http@go1.19.4
More info: https://pkg.go.dev/vuln/GO-2022-1144
Vulnerability #2: GO-2022-1039
Programs which compile regular expressions from untrusted
sources may be vulnerable to memory exhaustion or denial of
service. The parsed regexp representation is linear in the size
of the input, but in some cases the constant factor can be as
high as 40,000, making relatively small regexps consume much
larger amounts of memory. After fix, each regexp being parsed is
limited to a 256 MB memory footprint. Regular expressions whose
representation would use more space than that are rejected.
Normal use of regular expressions is unaffected.
Call stacks in your code:
github.com/semaphoreci-demos/semaphore-demo-go.init calls github.com/lib/pq.init, which eventually calls regexp/syntax.Parse
Found in: regexp/syntax@go1.19
Fixed in: regexp/syntax@go1.19.2
More info: https://pkg.go.dev/vuln/GO-2022-1039
Vulnerability #3: GO-2022-0969
HTTP/2 server connections can hang forever waiting for a clean
shutdown that was preempted by a fatal error. This condition can
be exploited by a malicious client to cause a denial of service.
Call stacks in your code:
main.go:76:28: github.com/semaphoreci-demos/semaphore-demo-go.main calls net/http.ListenAndServe
Found in: net/http@go1.19
Fixed in: net/http@go1.19.1
More info: https://pkg.go.dev/vuln/GO-2022-0969
=== Informational ===The vulnerabilities below are in packages that you import, but your code
doesn't appear to call any vulnerable functions. You may not need to take any
action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck
for details.
Vulnerability #1: GO-2022-1143
On Windows, restricted files can be accessed via os.DirFS and
http.Dir. The os.DirFS function and http.Dir type provide access
to a tree of files rooted at a given directory. These functions
permit access to Windows device files under that root. For
example, os.DirFS("C:/tmp").Open("COM1") opens the COM1 device.
Both os.DirFS and http.Dir only provide read-only filesystem
access. In addition, on Windows, an os.DirFS for the directory
(the root of the current drive) can permit a maliciously crafted
path to escape from the drive and access any path on the system.
With fix applied, the behavior of os.DirFS("") has changed.
Previously, an empty root was treated equivalently to "/", so
os.DirFS("").Open("tmp") would open the path "/tmp". This now
returns an error.
Found in: net/http@go1.19
Fixed in: net/http@go1.19.4
More info: https://pkg.go.dev/vuln/GO-2022-1143
Vulnerability #2: GO-2022-1095
Due to unsanitized NUL values, attackers may be able to
maliciously set environment variables on Windows. In
syscall.StartProcess and os/exec.Cmd, invalid environment
variable values containing NUL values are not properly checked
for. A malicious environment variable value can exploit this
behavior to set a value for a different environment variable.
For example, the environment variable string "A=B\x00C=D" sets
the variables "A=B" and "C=D".
Found in: os/exec@go1.19
Fixed in: os/exec@go1.19.3
More info: https://pkg.go.dev/vuln/GO-2022-1095
Vulnerability #3: GO-2022-0988
JoinPath and URL.JoinPath do not remove ../ path elements
appended to a relative path. For example,
JoinPath("https://go.dev", "../go") returns the URL
"https://go.dev/../go", despite the JoinPath documentation
stating that ../ path elements are removed from the result.
Found in: net/url@go1.19
Fixed in: net/url@go1.19.1
More info: https://pkg.go.dev/vuln/GO-2022-0988

Govulncheck reveals that I have three security issues in the project; all of them can be resolved by switching to a newer Go version, as the affected packages belong to the Go standard library. The tool can also warn us about other problems in imported modules. These are “informational” messages because we’re not using the affected functions, so we should be safe.

Govulncheck quirks

Govulncheck is an experimental tool, so finding a few quirks shouldn’t be too surprising. During the creation of this tutorial we found two issues.

The first one appeared in projects using C extensions. Importing C code can make govulncheck fail:

$ govulncheck ./...
Scanning for dependencies with known vulnerabilities...
govulncheck: Packages contain errors:
/usr/local/go/src/runtime/cgo/cgo.go:33:8: could not import C (no metadata for C)
/usr/local/go/src/os/user/cgo_listgroups_unix.go:19:8: could not import C (no metadata for C)
/usr/local/go/src/net/cgo_linux.go:12:8: could not import C (no metadata for C)

The workaround we found is to disable the cgo package with: export CGO_ENABLED=0.

The second problem we encountered was high memory usage. According to this issue: govulncheck ate all available memory and got killed; govulncheck builds a call graph in memory that uses up a lot of it in big projects. So, before adding govulncheck to your project, do some trial runs to see how much memory it needs. If you experience this problem, you can use a system with more memory (e.g. choosing a bigger CI machine) or run govulncheck on smaller subsets of the project’s code.

Using govulncheck in the CI pipeline

The main benefit of having a CLI tool like govulncheck is that we can integrate it with our continuous integration pipeline. Implementing the integration lets everyone on the team know when a security issue emerges in the project, reducing the chance of releasing vulnerable software.

Adding govulncheck to the CI pipeline is very straightforward. The demo repository I cloned earlier already has a started pipeline, so I only need to add one job at the end.

We can add govulncheck into our CI pipeline by creating a new job. In this section, we’ll use the example pipeline in the semaphoreci-demo/semaphore-demo-go we forked earlier. The project already has a starter pipeline that looks like this:

The job to create has the commands shown below. Adjust the sem-versioncommand as needed. We’ll choose Go 1.19 to verify that the job fails.

checkout
sem-version go 1.19
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...

After the changes, the pipeline will look like this:

Running the pipeline should throw an error since we’ve not yet fixed the security issues.

Checking the job log reveals the same error that we encountered the first time.

In this example, we can fix the problem by changing sem-version go 1.19 to sem-version go 1.19.4 in all the jobs in the CI pipeline.

Conclusion

We dare not live without automated security scanning, especially in a system-level language like Go. Govulncheck may not have the catchiest of names and, being in the experimental stage, it has its share of troubles, but there is no doubt that the Go security team made a giant leap forward with its release.

If this tool looks useful to you, check out the official VS Code extension, which lets you run security checks right in the IDE.

Thanks for reading!

Originally published at https://semaphoreci.com on February 16, 2023.

--

--

Semaphore

Supporting developers with insights and tutorials on delivering good software. · https://semaphoreci.com