Hi,
I've been writing my first RPM spec[0] for one of my projects[1]. After running rpmbuild, I something peculiar: the resulting binaries were dynamically-linked according to the "file" command, even though I took care to never import anything that could enable CGO. The same thing happened to other non-CGO binaries in the Fedora repos, such as fzf and shfmt. I managed to isolate the cause to the %gocompilerflags macro setting -buildmode=pie.
[0] https://git.sr.ht/~seirdy/fedora-specs/tree/master/item/golang-sr-seirdy-moa... [1] https://sr.ht/~seirdy/MOAC
The %gobuild macro sets -buildmode=PIE as of F26[2]. However, -buildmode=PIE doesn't necessarily improve the security of a Go binary.
Russel Cox and Ian Lance Taylor share some information in the golang-nuts list [3].
[2] https://fedoraproject.org/wiki/Changes/golang-buildmode-pie [3] https://groups.google.com/g/golang-nuts/c/Jd9tlNc6jUE/m/qp2oyfEEfjQJ
Although the full thread is worth a read, Ian's messages towards the end were the most relevant.
The view of the Go authors is that PIE makes sense for C/C++ toolchains, but it isn't as relevant for the Go runtime. The Go runtime uses a fixed memory address regardless of the build mode. What buildmode=pie does is introduce position-independent code to make the binary look like a PIE and satisfy the "PIE-only" requirement of many operating systems. It's a box-ticker but doesn't actually serve as an exploit mitigation.
PIE is useful when a binary links in C code with CGO, but for Go packages that make no use of CGO (shfmt, fzf, etc), PIE doesn't offer tangible security benefits.
What buildmode=pie does accomplish for otherwise-non-CGO bins is the introduction of additional complexity. Pure-Go bins now need a C toolchain and are dynlinked against /lib64/ld-linux-%{arch}.so.2. If anything, this introduces additional attack surface.
One of the main advantages of Linux is the relative stability of its kernel ABI; we should make use of that by allowing binaries to be independent of the system libc when reasonable.
Proposal:
Introduce macros for building Go packages without CGO. Unlike the existing %gobuildflags and %gocompilerflags macros, they should:
- set "-linkmode internal" in -ldflags - set the variable CGO_ENABLED=0 - set -buildmode=default - omit extldflags (since no C code and internal linking means no C toolchain)
This should produce statically-linked Go binaries independent of the system libc/ld.
A more speculative follow-up idea I had:
Packages could define whether they will need CGO and why: for example, Caddy would need CGO to use the OS networking stacks (e.g. for DNS), Hugo would need CGO for building with SASS support via libsass, etc.
Simply importing something like net/http enables CGO by default, but this is not strictly necessary; while such features would be useful to Caddy, a hypothetical tiny Go program that simply needs to perform a basic HTTP request doesn't need the advanced CGO-only net/http package.
I also brought up this topic in #alpine-devel on OFTC; I've attached the relevant logs, which brought up some interesting points. Perhaps we should reach out to Ian or Russel from the Go team to weigh in.
golang@lists.fedoraproject.org