Skip to content

Commit 1dcb583

Browse files
rscRuss Cox
authored and
Russ Cox
committed
cmd/go: accept only limited compiler and linker flags in #cgo directives
Both gcc and clang accept an option -fplugin=code.so to load a plugin from the ELF shared object file code.so. Obviously that plugin can then do anything it wants during the build. This is contrary to the goal of "go get" never running untrusted code during the build. (What happens if you choose to run the result of the build is your responsibility.) Disallow this behavior by only allowing a small set of known command-line flags in #cgo CFLAGS directives (and #cgo LDFLAGS, etc). The new restrictions can be adjusted by the environment variables CGO_CFLAGS_ALLOW, CGO_CFLAGS_DISALLOW, and so on. See the documentation. In addition to excluding cgo-defined flags, we also have to make sure that when we pass file names on the command line, they don't look like flags. So we now refuse to build packages containing suspicious file names like -x.go. A wrinkle in all this is that GNU binutils uniformly accept @foo on the command line to mean "if the file foo exists, then substitute its contents for @foo in the command line". So we must also reject @x.go, flags and flag arguments beginning with @, and so on. Fixes #23672, CVE-2018-6574. Change-Id: I59e7c1355155c335a5c5ae0d2cf8fa7aa313940a Reviewed-on: https://team-review.git.corp.google.com/209949 Reviewed-by: Ian Lance Taylor <iant@google.com>
1 parent b2d3d6e commit 1dcb583

File tree

12 files changed

+780
-60
lines changed

12 files changed

+780
-60
lines changed

misc/cgo/errors/src/err1.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
package main
66

77
/*
8-
#cgo LDFLAGS: -c
8+
#cgo LDFLAGS: -L/nonexist
99
1010
void test() {
1111
xxx; // ERROR HERE

src/cmd/cgo/doc.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ For example:
4545
// #include <png.h>
4646
import "C"
4747
48-
Alternatively, CPPFLAGS and LDFLAGS may be obtained via the pkg-config
49-
tool using a '#cgo pkg-config:' directive followed by the package names.
48+
Alternatively, CPPFLAGS and LDFLAGS may be obtained via the pkg-config tool
49+
using a '#cgo pkg-config:' directive followed by the package names.
5050
For example:
5151
5252
// #cgo pkg-config: png cairo
@@ -55,11 +55,21 @@ For example:
5555
5656
The default pkg-config tool may be changed by setting the PKG_CONFIG environment variable.
5757
58+
For security reasons, only a limited set of flags are allowed, notably -D, -I, and -l.
59+
To allow additional flags, set CGO_CFLAGS_ALLOW to a regular expression
60+
matching the new flags. To disallow flags that would otherwise be allowed,
61+
set CGO_CFLAGS_DISALLOW to a regular expression matching arguments
62+
that must be disallowed. In both cases the regular expression must match
63+
a full argument: to allow -mfoo=bar, use CGO_CFLAGS_ALLOW='-mfoo.*',
64+
not just CGO_CFLAGS_ALLOW='-mfoo'. Similarly named variables control
65+
the allowed CPPFLAGS, CXXFLAGS, FFLAGS, and LDFLAGS.
66+
5867
When building, the CGO_CFLAGS, CGO_CPPFLAGS, CGO_CXXFLAGS, CGO_FFLAGS and
5968
CGO_LDFLAGS environment variables are added to the flags derived from
6069
these directives. Package-specific flags should be set using the
6170
directives, not the environment variables, so that builds work in
62-
unmodified environments.
71+
unmodified environments. Flags obtained from environment variables
72+
are not subject to the security limitations described above.
6373
6474
All the cgo CPPFLAGS and CFLAGS directives in a package are concatenated and
6575
used to compile C files in that package. All the CPPFLAGS and CXXFLAGS

src/cmd/compile/internal/gc/noder.go

+16
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package gc
77
import (
88
"fmt"
99
"os"
10+
"path/filepath"
1011
"runtime"
1112
"strconv"
1213
"strings"
@@ -1346,6 +1347,11 @@ func (p *noder) pragma(pos src.Pos, text string) syntax.Pragma {
13461347
p.linknames = append(p.linknames, linkname{pos, f[1], f[2]})
13471348

13481349
case strings.HasPrefix(text, "go:cgo_"):
1350+
// For security, we disallow //go:cgo_* directives outside cgo-generated files.
1351+
// Exception: they are allowed in the standard library, for runtime and syscall.
1352+
if !isCgoGeneratedFile(pos) && !compiling_std {
1353+
p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s only allowed in cgo-generated code", text)})
1354+
}
13491355
p.pragcgobuf += p.pragcgo(pos, text)
13501356
fallthrough // because of //go:cgo_unsafe_args
13511357
default:
@@ -1367,6 +1373,16 @@ func (p *noder) pragma(pos src.Pos, text string) syntax.Pragma {
13671373
return 0
13681374
}
13691375

1376+
// isCgoGeneratedFile reports whether pos is in a file
1377+
// generated by cgo, which is to say a file with name
1378+
// beginning with "_cgo_". Such files are allowed to
1379+
// contain cgo directives, and for security reasons
1380+
// (primarily misuse of linker flags), other files are not.
1381+
// See golang.org/issue/23672.
1382+
func isCgoGeneratedFile(pos src.Pos) bool {
1383+
return strings.HasPrefix(filepath.Base(filepath.Clean(pos.AbsFilename())), "_cgo_")
1384+
}
1385+
13701386
func mkname(sym *types.Sym) *Node {
13711387
n := oldname(sym)
13721388
if n.Name != nil && n.Name.Pack != nil {

src/cmd/dist/build.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -784,7 +784,7 @@ func runInstall(dir string, ch chan struct{}) {
784784
} else {
785785
archive = b
786786
}
787-
compile := []string{pathf("%s/compile", tooldir), "-pack", "-o", b, "-p", pkg}
787+
compile := []string{pathf("%s/compile", tooldir), "-std", "-pack", "-o", b, "-p", pkg}
788788
if gogcflags != "" {
789789
compile = append(compile, strings.Fields(gogcflags)...)
790790
}

src/cmd/go/alldocs.go

+20-11
Original file line numberDiff line numberDiff line change
@@ -1227,17 +1227,26 @@
12271227
// CGO_CFLAGS
12281228
// Flags that cgo will pass to the compiler when compiling
12291229
// C code.
1230-
// CGO_CPPFLAGS
1231-
// Flags that cgo will pass to the compiler when compiling
1232-
// C or C++ code.
1233-
// CGO_CXXFLAGS
1234-
// Flags that cgo will pass to the compiler when compiling
1235-
// C++ code.
1236-
// CGO_FFLAGS
1237-
// Flags that cgo will pass to the compiler when compiling
1238-
// Fortran code.
1239-
// CGO_LDFLAGS
1240-
// Flags that cgo will pass to the compiler when linking.
1230+
// CGO_CFLAGS_ALLOW
1231+
// A regular expression specifying additional flags to allow
1232+
// to appear in #cgo CFLAGS source code directives.
1233+
// Does not apply to the CGO_CFLAGS environment variable.
1234+
// CGO_CFLAGS_DISALLOW
1235+
// A regular expression specifying flags that must be disallowed
1236+
// from appearing in #cgo CFLAGS source code directives.
1237+
// Does not apply to the CGO_CFLAGS environment variable.
1238+
// CGO_CPPFLAGS, CGO_CPPFLAGS_ALLOW, CGO_CPPFLAGS_DISALLOW
1239+
// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
1240+
// but for the C preprocessor.
1241+
// CGO_CXXFLAGS, CGO_CXXFLAGS_ALLOW, CGO_CXXFLAGS_DISALLOW
1242+
// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
1243+
// but for the C++ compiler.
1244+
// CGO_FFLAGS, CGO_FFLAGS_ALLOW, CGO_FFLAGS_DISALLOW
1245+
// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
1246+
// but for the Fortran compiler.
1247+
// CGO_LDFLAGS, CGO_LDFLAGS_ALLOW, CGO_LDFLAGS_DISALLOW
1248+
// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
1249+
// but for the linker.
12411250
// CXX
12421251
// The command to use to compile C++ code.
12431252
// PKG_CONFIG

src/cmd/go/go_test.go

+150-1
Original file line numberDiff line numberDiff line change
@@ -2789,7 +2789,7 @@ func TestCgoHandlesWlORIGIN(t *testing.T) {
27892789
defer tg.cleanup()
27902790
tg.parallel()
27912791
tg.tempFile("src/origin/origin.go", `package origin
2792-
// #cgo !darwin LDFLAGS: -Wl,-rpath -Wl,$ORIGIN
2792+
// #cgo !darwin LDFLAGS: -Wl,-rpath,$ORIGIN
27932793
// void f(void) {}
27942794
import "C"
27952795
func f() { C.f() }`)
@@ -5739,3 +5739,152 @@ func TestAtomicCoverpkgAll(t *testing.T) {
57395739
tg.run("test", "-coverpkg=all", "-race", "x")
57405740
}
57415741
}
5742+
5743+
func TestBadCommandLines(t *testing.T) {
5744+
tg := testgo(t)
5745+
defer tg.cleanup()
5746+
5747+
tg.tempFile("src/x/x.go", "package x\n")
5748+
tg.setenv("GOPATH", tg.path("."))
5749+
5750+
tg.run("build", "x")
5751+
5752+
tg.tempFile("src/x/@y.go", "package x\n")
5753+
tg.runFail("build", "x")
5754+
tg.grepStderr("invalid input file name \"@y.go\"", "did not reject @y.go")
5755+
tg.must(os.Remove(tg.path("src/x/@y.go")))
5756+
5757+
tg.tempFile("src/x/-y.go", "package x\n")
5758+
tg.runFail("build", "x")
5759+
tg.grepStderr("invalid input file name \"-y.go\"", "did not reject -y.go")
5760+
tg.must(os.Remove(tg.path("src/x/-y.go")))
5761+
5762+
tg.runFail("build", "-gcflags=all=@x", "x")
5763+
tg.grepStderr("invalid command-line argument @x in command", "did not reject @x during exec")
5764+
5765+
tg.tempFile("src/@x/x.go", "package x\n")
5766+
tg.setenv("GOPATH", tg.path("."))
5767+
tg.runFail("build", "@x")
5768+
tg.grepStderr("invalid input directory name \"@x\"", "did not reject @x directory")
5769+
5770+
tg.tempFile("src/@x/y/y.go", "package y\n")
5771+
tg.setenv("GOPATH", tg.path("."))
5772+
tg.runFail("build", "@x/y")
5773+
tg.grepStderr("invalid import path \"@x/y\"", "did not reject @x/y import path")
5774+
5775+
tg.tempFile("src/-x/x.go", "package x\n")
5776+
tg.setenv("GOPATH", tg.path("."))
5777+
tg.runFail("build", "--", "-x")
5778+
tg.grepStderr("invalid input directory name \"-x\"", "did not reject -x directory")
5779+
5780+
tg.tempFile("src/-x/y/y.go", "package y\n")
5781+
tg.setenv("GOPATH", tg.path("."))
5782+
tg.runFail("build", "--", "-x/y")
5783+
tg.grepStderr("invalid import path \"-x/y\"", "did not reject -x/y import path")
5784+
}
5785+
5786+
func TestBadCgoDirectives(t *testing.T) {
5787+
if !canCgo {
5788+
t.Skip("no cgo")
5789+
}
5790+
tg := testgo(t)
5791+
defer tg.cleanup()
5792+
5793+
tg.tempFile("src/x/x.go", "package x\n")
5794+
tg.setenv("GOPATH", tg.path("."))
5795+
5796+
tg.tempFile("src/x/x.go", `package x
5797+
5798+
//go:cgo_ldflag "-fplugin=foo.so"
5799+
5800+
import "C"
5801+
`)
5802+
tg.runFail("build", "x")
5803+
tg.grepStderr("//go:cgo_ldflag .* only allowed in cgo-generated code", "did not reject //go:cgo_ldflag directive")
5804+
5805+
tg.must(os.Remove(tg.path("src/x/x.go")))
5806+
tg.runFail("build", "x")
5807+
tg.grepStderr("no Go files", "did not report missing source code")
5808+
tg.tempFile("src/x/_cgo_yy.go", `package x
5809+
5810+
//go:cgo_ldflag "-fplugin=foo.so"
5811+
5812+
import "C"
5813+
`)
5814+
tg.runFail("build", "x")
5815+
tg.grepStderr("no Go files", "did not report missing source code") // _* files are ignored...
5816+
5817+
tg.runFail("build", tg.path("src/x/_cgo_yy.go")) // ... but if forced, the comment is rejected
5818+
// Actually, today there is a separate issue that _ files named
5819+
// on the command-line are ignored. Once that is fixed,
5820+
// we want to see the cgo_ldflag error.
5821+
tg.grepStderr("//go:cgo_ldflag only allowed in cgo-generated code|no Go files", "did not reject //go:cgo_ldflag directive")
5822+
tg.must(os.Remove(tg.path("src/x/_cgo_yy.go")))
5823+
5824+
tg.tempFile("src/x/x.go", "package x\n")
5825+
tg.tempFile("src/x/y.go", `package x
5826+
// #cgo CFLAGS: -fplugin=foo.so
5827+
import "C"
5828+
`)
5829+
tg.runFail("build", "x")
5830+
tg.grepStderr("invalid flag in #cgo CFLAGS: -fplugin=foo.so", "did not reject -fplugin")
5831+
5832+
tg.tempFile("src/x/y.go", `package x
5833+
// #cgo CFLAGS: -Ibar -fplugin=foo.so
5834+
import "C"
5835+
`)
5836+
tg.runFail("build", "x")
5837+
tg.grepStderr("invalid flag in #cgo CFLAGS: -fplugin=foo.so", "did not reject -fplugin")
5838+
5839+
tg.tempFile("src/x/y.go", `package x
5840+
// #cgo pkg-config: -foo
5841+
import "C"
5842+
`)
5843+
tg.runFail("build", "x")
5844+
tg.grepStderr("invalid pkg-config package name: -foo", "did not reject pkg-config: -foo")
5845+
5846+
tg.tempFile("src/x/y.go", `package x
5847+
// #cgo pkg-config: @foo
5848+
import "C"
5849+
`)
5850+
tg.runFail("build", "x")
5851+
tg.grepStderr("invalid pkg-config package name: @foo", "did not reject pkg-config: -foo")
5852+
5853+
tg.tempFile("src/x/y.go", `package x
5854+
// #cgo CFLAGS: @foo
5855+
import "C"
5856+
`)
5857+
tg.runFail("build", "x")
5858+
tg.grepStderr("invalid flag in #cgo CFLAGS: @foo", "did not reject @foo flag")
5859+
5860+
tg.tempFile("src/x/y.go", `package x
5861+
// #cgo CFLAGS: -D
5862+
import "C"
5863+
`)
5864+
tg.runFail("build", "x")
5865+
tg.grepStderr("invalid flag in #cgo CFLAGS: -D without argument", "did not reject trailing -I flag")
5866+
5867+
// Note that -I @foo is allowed because we rewrite it into -I /path/to/src/@foo
5868+
// before the check is applied. There's no such rewrite for -D.
5869+
5870+
tg.tempFile("src/x/y.go", `package x
5871+
// #cgo CFLAGS: -D @foo
5872+
import "C"
5873+
`)
5874+
tg.runFail("build", "x")
5875+
tg.grepStderr("invalid flag in #cgo CFLAGS: -D @foo", "did not reject -D @foo flag")
5876+
5877+
tg.tempFile("src/x/y.go", `package x
5878+
// #cgo CFLAGS: -D@foo
5879+
import "C"
5880+
`)
5881+
tg.runFail("build", "x")
5882+
tg.grepStderr("invalid flag in #cgo CFLAGS: -D@foo", "did not reject -D@foo flag")
5883+
5884+
tg.setenv("CGO_CFLAGS", "-D@foo")
5885+
tg.tempFile("src/x/y.go", `package x
5886+
import "C"
5887+
`)
5888+
tg.run("build", "-n", "x")
5889+
tg.grepStderr("-D@foo", "did not find -D@foo in commands")
5890+
}

src/cmd/go/internal/envcmd/env.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,12 @@ func findEnv(env []cfg.EnvVar, name string) string {
113113
func ExtraEnvVars() []cfg.EnvVar {
114114
var b work.Builder
115115
b.Init()
116-
cppflags, cflags, cxxflags, fflags, ldflags := b.CFlags(&load.Package{})
116+
cppflags, cflags, cxxflags, fflags, ldflags, err := b.CFlags(&load.Package{})
117+
if err != nil {
118+
// Should not happen - b.CFlags was given an empty package.
119+
fmt.Fprintf(os.Stderr, "go: invalid cflags: %v\n", err)
120+
return nil
121+
}
117122
cmd := b.GccCmd(".", "")
118123
return []cfg.EnvVar{
119124
// Note: Update the switch in runEnv below when adding to this list.

src/cmd/go/internal/help/helpdoc.go

+20-11
Original file line numberDiff line numberDiff line change
@@ -487,17 +487,26 @@ Environment variables for use with cgo:
487487
CGO_CFLAGS
488488
Flags that cgo will pass to the compiler when compiling
489489
C code.
490-
CGO_CPPFLAGS
491-
Flags that cgo will pass to the compiler when compiling
492-
C or C++ code.
493-
CGO_CXXFLAGS
494-
Flags that cgo will pass to the compiler when compiling
495-
C++ code.
496-
CGO_FFLAGS
497-
Flags that cgo will pass to the compiler when compiling
498-
Fortran code.
499-
CGO_LDFLAGS
500-
Flags that cgo will pass to the compiler when linking.
490+
CGO_CFLAGS_ALLOW
491+
A regular expression specifying additional flags to allow
492+
to appear in #cgo CFLAGS source code directives.
493+
Does not apply to the CGO_CFLAGS environment variable.
494+
CGO_CFLAGS_DISALLOW
495+
A regular expression specifying flags that must be disallowed
496+
from appearing in #cgo CFLAGS source code directives.
497+
Does not apply to the CGO_CFLAGS environment variable.
498+
CGO_CPPFLAGS, CGO_CPPFLAGS_ALLOW, CGO_CPPFLAGS_DISALLOW
499+
Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
500+
but for the C preprocessor.
501+
CGO_CXXFLAGS, CGO_CXXFLAGS_ALLOW, CGO_CXXFLAGS_DISALLOW
502+
Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
503+
but for the C++ compiler.
504+
CGO_FFLAGS, CGO_FFLAGS_ALLOW, CGO_FFLAGS_DISALLOW
505+
Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
506+
but for the Fortran compiler.
507+
CGO_LDFLAGS, CGO_LDFLAGS_ALLOW, CGO_LDFLAGS_DISALLOW
508+
Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
509+
but for the linker.
501510
CXX
502511
The command to use to compile C++ code.
503512
PKG_CONFIG

0 commit comments

Comments
 (0)