*Published: June 2024*

TL;DR

If you use Go with the gccgo compiler, a security bug in how go handles #cgo LDFLAGS could let a malicious module run arbitrary code while building, NOT just running, your code. Think running go get and suddenly you’ve been pwned.

Let's break down what happened, how this works—including a code example—and what you need to do to stay safe.

The Root of the Problem

When building Go projects that use native/C code via cgo, you can specify compiler and linker flags. For security, Go tries to sanitize the flags provided in #cgo LDFLAGS so you can't slip in something dangerous.

But! With gccgo (an alternate Go compiler besides the default gc one), there's a bug: if an attacker puts a flag with embedded spaces, the sanitization is fooled, and unsafe linker flags can sneak through—possibly running shell commands.

Go team announcement:  
https://groups.google.com/g/golang-announce/c/o99oUy1K2Uc

Imagine you run this innocent-looking Go command

go get github.com/badguy/evilmodule

If that module has a cgo section like below

// #cgo LDFLAGS: -Xlinker "-B /tmp; touch /tmp/pwned"
import "C"

The LDFLAGS line (especially with the embedded space and extra shell commands) *should* be stopped by Go's filtering. But with the bug in gccgo, the whole thing is passed along, allowing the touch /tmp/pwned part to run during linking.

Here's what an attacker might put in a Go package to exploit the bug

package main

// #cgo LDFLAGS: -Wl,-rpath,/trusted -Wl,-B /tmp; touch /tmp/pwned
import "C"

func main() {}

The clever part here is "smuggling" the dangerous bits inside what looks like a valid flag to evade Go's sanitization.

Why Only gccgo?

- The main Go compiler (gc) doesn't use the same linker or parse the flags the same way, so this trick doesn't work there.
- If you use the default go toolchain (go1.19, go1.20, etc) and don't explicitly use gccgo, you're probably not at risk.
- Some Linux distros and enterprise builds might default to gccgo for compatibility or policy reasons. If that's you—watch out!

evilmodule/evil.go

package evil

// #cgo LDFLAGS: -Wl,-B /tmp; touch /tmp/pwned
import "C"

Then, if someone fetches/built this with go get (using gccgo), the build kicks off and the attack drops a pwned marker into /tmp (or runs any command the attacker likes).

Impact

- Arbitrary code execution: Running go get or building code that includes malicious dependencies can execute commands with your privileges.
- Supply chain risk: Any developer fetching untrusted third-party modules from the public internet, especially from untrusted or new sources, is at risk.
- CI/CD attack surface: Build servers often run automation as trusted system users—compromise here can mean full system access.

References & Patches

- Official Go Security Announcement: CVE-2023-29405
- NIST NVD Entry: CVE-2023-29405
- Go GitHub issue tracker

Go 1.19.9

If you use gccgo, ensure it's patched and does not process unsafe LDFLAGS from untrusted modules anymore.

Mitigations

1. Prefer the default Go compiler (gc)—especially for anything pulling code from public sources.

Audit your dependencies BEFORE building.

4. Never build modules you don't trust, especially those with native/C bindings.
5. In CI/CD scripts, set CGO_ENABLED= if you don't need cgo.

Conclusion

CVE-2023-29405 is a classic example of how small parsing bugs in build tools can lead to major supply chain vulnerabilities. If you're on a team or project that uses non-standard Go toolchains, take this one seriously and review your build environments.

Stay safe and happy hacking!

*If you liked this long read, share it with someone using Go and remind them to go patch their toolchain!*

Timeline

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