go get a package, Go is designed to build and install the package without running any code from it. The intent is that you can safely get, build, and even install Go packages without trusting them at all. In theory, it seems like this should be a simple feat for the Go toolchain, but in actuality, it's a real challenge.
go get does a lot under the hood, including invoking third-party tools like
clang in ways that are heavily influenced by package configurations. Ensuring that these invocations are safe is an uphill battle that Go hasn't quite won yet.
A History of Arbitrary Code Execution
Go 1.9.1 (CVE-2017–15041)
In 2017, Simon Rawet reported a bug that allowed arbitrary code execution through nested repositories (#22125). By committing an entire Git repo to an SVN repository,
git could be tricked into running hooks within that repo.
This was assigned a CVSS 3.1 score of 9.8 (nearly as high as it gets) and would be the first of many arbitrary code execution issues.
Go 1.9.4 (CVE-2018–6574)
In 2018, I found that by passing malicious flags to the compiler through
#cgo directives, I could cause
gcc to run arbitrary code on
go get (#23672).
The fix for this one is especially significant: The Go team mitigated this by restricting compiler and linker arguments to an allow-list. One reason this is significant is because it was a breaking change — Generally, any package that compiles for Go 1.0.0 can be compiled with the latest Go release, but this security patch broke many packages that used cgo.
Go 1.11.3 (CVE-2018–16873)
In late 2018, Etienne Stalmans from the Heroku platform security team discovered a bug that exploited
git into running malicious commands (#29230). This works somewhat similarly to the first Git exploit in the sense that it tricks
go get into copying a malicious .git directory to the filesystem then running
git commands that use it.
Go 1.11.3 (CVE-2018–16874)
At the same time, ztz of Tencent Security Platform discovered that using curly braces in import paths allowed him to do arbitrary filesystem writes on
go get (#29231). This can allow for arbitrary code execution in several ways (e.g. by placing executables in PATH or writing hooks to .git directories).
Go 1.15.5 (CVE-2020–28366)
This past month, I took some time to revisit the cgo flag attack vector and found that I could inject arbitrary linker flags into a build by crafting libraries with malicious symbol names (#42559).
Go 1.15.5 (CVE-2020–28367)
And at the same time, Imre Rad discovered that the validation done on cgo flags wasn’t adequate (#42556).
Is This Actually a Security Problem?
You might be wondering if this is actually a problem. After all, other languages allow arbitrary code execution at build time. In some languages, like Rust, it’s considered a feature. So what’s the difference?
The difference is that the Go maintainers say arbitrary code execution at build time shouldn’t be possible. That’s all. If they say you can build untrusted code safely, people will rely on being able to do so. For people relying on that, there are serious security implications when third parties can run code in places where they aren’t expected to.
This likely isn’t the end of these issues. I can’t say why the Go maintainers have chosen their particular threat model, but I can say that no one should trust their Go builds any more than they trust their Rust builds. It would be a good bet that there are more arbitrary code execution vulnerabilities waiting to be found (If you find any of them yourself, you can report them and earn rewards from Google’s Vulnerability Reward Program!).
None of this is a dig at Go in any way. The Go team has done a fantastic job responding to and resolving reports, and Go continues to be one of our favorite languages here at Tempus Ex. This is just a reminder to exercise layered security. 😎
If you like working with Go and you’re open to new jobs, Tempus Ex is hiring! Come join us and get paid to work on cool things like what you see here!