Product

Go 1.20 is coming, and it brings even more security by default

Adrian Mouat, Staff OSS Engineer
January 24, 2023
copied

While the core language is stable, every new Go release brings with it a bunch of new features and Go 1.20 is one of the most exciting releases yet. This blog post outlines some of the new features in Go, with a focus on security and reliability.

New Coverage Implementation

We all know that simple code bases are easier to secure, and cruft has a way of building up over time. Go already makes unused variables a compile-time error, but other forms of unused code can still lurk in your codebase, increasing compile time, clogging up test pipelines, pulling in dependencies, and making refactoring harder.

Go’s code coverage system has been a great way to check which code paths are called during a unit test run, but the implementation of this system made it impossible to use during actual program execution. This made coverage hard to track during end-to-end testing or even in canary or production environments.

This new feature changes everything. The Go team re-designed and re-implemented the code coverage system so it can work in compiled binaries, meaning you can now see full coverage reports during integration testing, or even instrument production code! I hope some tooling emerges to track unused code against a small percentage of production traffic, making it easier to remove deprecated or unused functionality and keep your codebase slim and secure.

Tar file handling

This is a subtle change that will make an entire class of CVEs much harder. To understand the problem here, we have to dive into the tarfile specification a bit. A tarfile is a stream of records, where each record contains the file name, metadata, and contents about that file. To extract a tarfile, one typically writes a short program like this:

-- CODE language-bash -- package main import ( "archive/tar" "io" "log" "os" "path/filepath" ) func main() { // Open the tar file for reading tarFile, err := os.Open("path/to/tar/file.tar") if err != nil { log.Fatal(err) } defer tarFile.Close() // Create a new tar reader tarReader := tar.NewReader(tarFile) // Iterate through the files in the tar for { header, err := tarReader.Next() if err == io.EOF { // End of tar archive break } if err != nil { log.Fatal(err) } // Get the file info info := header.FileInfo() // Get the file name and create the file filename := info.Name() file, err := os.Create(filename) if err != nil { log.Fatal(err) } defer file.Close() // Write the file data to disk _, err = io.Copy(file, tarReader) if err != nil { log.Fatal(err) } } }

But this program is vulnerable! Depending on the tar files you process, a file entry could contain a path like “/etc/passwd”, or even “/usr/bin/bash”. Blindly extracting the tar file leaves you vulnerable to overwriting critical system files. Doing this safely and correctly requires you to check that the file path is local rather than absolute. You also have to watch out for something like “../../../etc/passwd”.

These “sharp edges” crop up very often and bite unsuspecting users, so the Go team decided to introduce a change to the standard library making this function secure-by-default. Iterating through a tar file now throws an error if the extracted file is absolute, forcing the user to opt-in to this unsafe behavior rather than opt-out.

This type of secure-by-default functionality is why we love Go. The 1.20 release introduces this change as an experiment, and it could likely be turned on by default in a future release. Because we love it so much, we’re enabling it by default in the Go Chainguard Image. You can try out the 1.20 pre-release at cgr.dev/chainguard/go-1.20!

New Cryptography Features

Cryptography is another area where ergonomics matter greatly. Misuse resistant cryptography is the best kind, and the Go standard library contains some of the best crypto libraries out there because they were designed to be hard to use incorrectly. But still, some algorithms are easier to screw up than others. For digital signatures, ed25519 is widely considered to be the best algorithm because it’s the hardest to mess up.

You don’t need a secure random number generator or even a hash function when signing blobs with ed25519, removing two possible points to make a mistake. These aren’t just theoretical concerns either - the Playstation 3 DRM system was compromised because of an insecure random number generator used with the ECDSA signing algorithm.

Unfortunately, ed25519 doesn’t work everywhere yet. One reason for this is the lack of pre-hashing. It’s nice that you don’t have to select a (possibly weak) hashing algorithm, but the downside is that you have to pass the entire message to your signer. In some cases these messages can be quite large (think of signing a multi-gigabyte file or image), making this impractical over a network.

Thankfully there is a newish variant to ed25519 that does support pre-hashing, called ed25519ph. This method has recently become standardized, so Go added support for it directly to the crypto standard library. We look forward to using it in Sigstore and other places.

Chainguard Images

As mentioned above - we’ve made a pre-release version of go 1.20 available in Wolfi and in our Chainguard Images collection. As always, we ship a full, accurate SBOM with every image that contains precise metadata about package versions (including Go!). To try out the image, you can pull it at cgr.dev/chainguard/go:1.20 (oh, and we also support arm64 in addition to amd architectures!)

-- CODE language-bash -- $ docker run cgr.dev/chainguard/go:1.20 version go version go1.20rc1 linux/arm64

You can also check the SBOM using cosign:

-- CODE language-bash -- $ cosign download sbom --platform=arm64 cgr.dev/chainguard/go:1.20 "SPDXID": "SPDXRef-Package-go-1.20", "name": "go-1.20", "versionInfo": "1.20rc1-r0", "filesAnalyzed": true, "hasFiles": [ "SPDXRef-File-/usr/lib/go/bin/go", "SPDXRef-File-/usr/lib/go/bin/gofmt", "SPDXRef-File-/usr/lib/go/lib/time/README", "SPDXRef-File-/usr/lib/go/lib/time/mkzip.go", "SPDXRef-File-/usr/lib/go/lib/time/update.bash", "SPDXRef-File-/usr/lib/go/lib/time/zoneinfo.zip", "SPDXRef-File-/usr/lib/go/pkg/include/asm_amd64.h", "SPDXRef-File-/usr/lib/go/pkg/include/asm_ppc64x.h", "SPDXRef-File-/usr/lib/go/pkg/include/funcdata.h", "SPDXRef-File-/usr/lib/go/pkg/include/textflag.h",

Even though this image is meant for build pipelines rather than direct production usage, vulnerability scans are still important and we keep these images free of vulnerabilities. Try it out in your scanner and feel free to file bugs if you find anything.

Try out any of our Images today at github.com/chainguard-images, or get started with our Go image using documentation in Chainguard Academy. Chainguard Images are now available for Python, Bazel, Redis, curl, Git, Go, Jenkins, Postgres, Prometheus and more. We currently offer our public Chainguard Images catalog for no cost to users, which includes features like SBOMs, signatures and SLSA Build Level 2 provenance information. If your organization requires patching SLAs, older version support or Images for compliance requirements, we offer Standard and Custom subscription tiers. Contact our team to learn more. 

We are always looking for ways to improve our end user experience. If you have feedback or would like to submit a support issue you can reach out to us directly or file it here.

Update on our Chainguard Images Catalog: On August 16, 2023, we will be making changes to how Chainguard Image tags are pulled. Please see this announcement for further details about accessing our free, public Image catalog. 

Related articles

Ready to lock down your supply chain?

Talk to our customer obsessed, community-driven team.