Skip to content

Commit 867fb18

Browse files
rscRuss Cox
authored andcommitted
[release-branch.go1.9] 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 golang#23673, CVE-2018-6574. Change-Id: I59e7c1355155c335a5c5ae0d2cf8fa7aa313940a Reviewed-on: https://team-review.git.corp.google.com/212507 Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent c03ee19 commit 867fb18

File tree

12 files changed

+767
-57
lines changed

12 files changed

+767
-57
lines changed

misc/cgo/errors/err1.go

Lines changed: 1 addition & 1 deletion
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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 16 additions & 0 deletions
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"
@@ -1190,6 +1191,11 @@ func (p *noder) pragma(pos src.Pos, text string) syntax.Pragma {
11901191
p.linknames = append(p.linknames, linkname{pos, f[1], f[2]})
11911192

11921193
case strings.HasPrefix(text, "go:cgo_"):
1194+
// For security, we disallow //go:cgo_* directives outside cgo-generated files.
1195+
// Exception: they are allowed in the standard library, for runtime and syscall.
1196+
if !isCgoGeneratedFile(pos) && !compiling_std {
1197+
p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s only allowed in cgo-generated code", text)})
1198+
}
11931199
p.pragcgobuf += p.pragcgo(pos, text)
11941200
fallthrough // because of //go:cgo_unsafe_args
11951201
default:
@@ -1211,6 +1217,16 @@ func (p *noder) pragma(pos src.Pos, text string) syntax.Pragma {
12111217
return 0
12121218
}
12131219

1220+
// isCgoGeneratedFile reports whether pos is in a file
1221+
// generated by cgo, which is to say a file with name
1222+
// beginning with "_cgo_". Such files are allowed to
1223+
// contain cgo directives, and for security reasons
1224+
// (primarily misuse of linker flags), other files are not.
1225+
// See golang.org/issue/23672.
1226+
func isCgoGeneratedFile(pos src.Pos) bool {
1227+
return strings.HasPrefix(filepath.Base(filepath.Clean(pos.AbsFilename())), "_cgo_")
1228+
}
1229+
12141230
func mkname(sym *types.Sym) *Node {
12151231
n := oldname(sym)
12161232
if n.Name != nil && n.Name.Pack != nil {

src/cmd/dist/build.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,7 @@ func install(dir string) {
701701
} else {
702702
archive = b
703703
}
704-
compile := []string{pathf("%s/compile", tooldir), "-pack", "-o", b, "-p", pkg}
704+
compile := []string{pathf("%s/compile", tooldir), "-std", "-pack", "-o", b, "-p", pkg}
705705
if gogcflags != "" {
706706
compile = append(compile, strings.Fields(gogcflags)...)
707707
}

src/cmd/go/alldocs.go

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,17 +1106,26 @@
11061106
// CGO_CFLAGS
11071107
// Flags that cgo will pass to the compiler when compiling
11081108
// C code.
1109-
// CGO_CPPFLAGS
1110-
// Flags that cgo will pass to the compiler when compiling
1111-
// C or C++ code.
1112-
// CGO_CXXFLAGS
1113-
// Flags that cgo will pass to the compiler when compiling
1114-
// C++ code.
1115-
// CGO_FFLAGS
1116-
// Flags that cgo will pass to the compiler when compiling
1117-
// Fortran code.
1118-
// CGO_LDFLAGS
1119-
// Flags that cgo will pass to the compiler when linking.
1109+
// CGO_CFLAGS_ALLOW
1110+
// A regular expression specifying additional flags to allow
1111+
// to appear in #cgo CFLAGS source code directives.
1112+
// Does not apply to the CGO_CFLAGS environment variable.
1113+
// CGO_CFLAGS_DISALLOW
1114+
// A regular expression specifying flags that must be disallowed
1115+
// from appearing in #cgo CFLAGS source code directives.
1116+
// Does not apply to the CGO_CFLAGS environment variable.
1117+
// CGO_CPPFLAGS, CGO_CPPFLAGS_ALLOW, CGO_CPPFLAGS_DISALLOW
1118+
// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
1119+
// but for the C preprocessor.
1120+
// CGO_CXXFLAGS, CGO_CXXFLAGS_ALLOW, CGO_CXXFLAGS_DISALLOW
1121+
// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
1122+
// but for the C++ compiler.
1123+
// CGO_FFLAGS, CGO_FFLAGS_ALLOW, CGO_FFLAGS_DISALLOW
1124+
// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
1125+
// but for the Fortran compiler.
1126+
// CGO_LDFLAGS, CGO_LDFLAGS_ALLOW, CGO_LDFLAGS_DISALLOW
1127+
// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
1128+
// but for the linker.
11201129
// CXX
11211130
// The command to use to compile C++ code.
11221131
// PKG_CONFIG

src/cmd/go/go_test.go

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2449,7 +2449,7 @@ func TestCgoHandlesWlORIGIN(t *testing.T) {
24492449
defer tg.cleanup()
24502450
tg.parallel()
24512451
tg.tempFile("src/origin/origin.go", `package origin
2452-
// #cgo !darwin LDFLAGS: -Wl,-rpath -Wl,$ORIGIN
2452+
// #cgo !darwin LDFLAGS: -Wl,-rpath,$ORIGIN
24532453
// void f(void) {}
24542454
import "C"
24552455
func f() { C.f() }`)
@@ -4349,3 +4349,150 @@ func TestListTests(t *testing.T) {
43494349
t.Run("Example1", testWith("Example", "ExampleSimple"))
43504350
t.Run("Example2", testWith("Example", "ExampleWithEmptyOutput"))
43514351
}
4352+
4353+
func TestBadCommandLines(t *testing.T) {
4354+
tg := testgo(t)
4355+
defer tg.cleanup()
4356+
4357+
tg.tempFile("src/x/x.go", "package x\n")
4358+
tg.setenv("GOPATH", tg.path("."))
4359+
4360+
tg.run("build", "x")
4361+
4362+
tg.tempFile("src/x/@y.go", "package x\n")
4363+
tg.runFail("build", "x")
4364+
tg.grepStderr("invalid input file name \"@y.go\"", "did not reject @y.go")
4365+
tg.must(os.Remove(tg.path("src/x/@y.go")))
4366+
4367+
tg.tempFile("src/x/-y.go", "package x\n")
4368+
tg.runFail("build", "x")
4369+
tg.grepStderr("invalid input file name \"-y.go\"", "did not reject -y.go")
4370+
tg.must(os.Remove(tg.path("src/x/-y.go")))
4371+
4372+
tg.runFail("build", "-gcflags=@x", "x")
4373+
tg.grepStderr("invalid command-line argument @x in command", "did not reject @x during exec")
4374+
4375+
tg.tempFile("src/@x/x.go", "package x\n")
4376+
tg.setenv("GOPATH", tg.path("."))
4377+
tg.runFail("build", "@x")
4378+
tg.grepStderr("invalid input directory name \"@x\"", "did not reject @x directory")
4379+
4380+
tg.tempFile("src/@x/y/y.go", "package y\n")
4381+
tg.setenv("GOPATH", tg.path("."))
4382+
tg.runFail("build", "@x/y")
4383+
tg.grepStderr("invalid import path \"@x/y\"", "did not reject @x/y import path")
4384+
4385+
tg.tempFile("src/-x/x.go", "package x\n")
4386+
tg.setenv("GOPATH", tg.path("."))
4387+
tg.runFail("build", "--", "-x")
4388+
tg.grepStderr("invalid input directory name \"-x\"", "did not reject -x directory")
4389+
4390+
tg.tempFile("src/-x/y/y.go", "package y\n")
4391+
tg.setenv("GOPATH", tg.path("."))
4392+
tg.runFail("build", "--", "-x/y")
4393+
tg.grepStderr("invalid import path \"-x/y\"", "did not reject -x/y import path")
4394+
}
4395+
4396+
func TestBadCgoDirectives(t *testing.T) {
4397+
if !canCgo {
4398+
t.Skip("no cgo")
4399+
}
4400+
tg := testgo(t)
4401+
defer tg.cleanup()
4402+
4403+
tg.tempFile("src/x/x.go", "package x\n")
4404+
tg.setenv("GOPATH", tg.path("."))
4405+
4406+
tg.tempFile("src/x/x.go", `package x
4407+
4408+
//go:cgo_ldflag "-fplugin=foo.so"
4409+
4410+
`)
4411+
tg.runFail("build", "x")
4412+
tg.grepStderr("//go:cgo_ldflag .* only allowed in cgo-generated code", "did not reject //go:cgo_ldflag directive")
4413+
4414+
tg.must(os.Remove(tg.path("src/x/x.go")))
4415+
tg.runFail("build", "x")
4416+
tg.grepStderr("no Go files", "did not report missing source code")
4417+
tg.tempFile("src/x/_cgo_yy.go", `package x
4418+
4419+
//go:cgo_ldflag "-fplugin=foo.so"
4420+
4421+
`)
4422+
tg.runFail("build", "x")
4423+
tg.grepStderr("no Go files", "did not report missing source code") // _* files are ignored...
4424+
4425+
tg.runFail("build", tg.path("src/x/_cgo_yy.go")) // ... but if forced, the comment is rejected
4426+
// Actually, today there is a separate issue that _ files named
4427+
// on the command-line are ignored. Once that is fixed,
4428+
// we want to see the cgo_ldflag error.
4429+
tg.grepStderr("//go:cgo_ldflag only allowed in cgo-generated code|no Go files", "did not reject //go:cgo_ldflag directive")
4430+
tg.must(os.Remove(tg.path("src/x/_cgo_yy.go")))
4431+
4432+
tg.tempFile("src/x/x.go", "package x\n")
4433+
tg.tempFile("src/x/y.go", `package x
4434+
// #cgo CFLAGS: -fplugin=foo.so
4435+
import "C"
4436+
`)
4437+
tg.runFail("build", "x")
4438+
tg.grepStderr("invalid flag in #cgo CFLAGS: -fplugin=foo.so", "did not reject -fplugin")
4439+
4440+
tg.tempFile("src/x/y.go", `package x
4441+
// #cgo CFLAGS: -Ibar -fplugin=foo.so
4442+
import "C"
4443+
`)
4444+
tg.runFail("build", "x")
4445+
tg.grepStderr("invalid flag in #cgo CFLAGS: -fplugin=foo.so", "did not reject -fplugin")
4446+
4447+
tg.tempFile("src/x/y.go", `package x
4448+
// #cgo pkg-config: -foo
4449+
import "C"
4450+
`)
4451+
tg.runFail("build", "x")
4452+
tg.grepStderr("invalid pkg-config package name: -foo", "did not reject pkg-config: -foo")
4453+
4454+
tg.tempFile("src/x/y.go", `package x
4455+
// #cgo pkg-config: @foo
4456+
import "C"
4457+
`)
4458+
tg.runFail("build", "x")
4459+
tg.grepStderr("invalid pkg-config package name: @foo", "did not reject pkg-config: -foo")
4460+
4461+
tg.tempFile("src/x/y.go", `package x
4462+
// #cgo CFLAGS: @foo
4463+
import "C"
4464+
`)
4465+
tg.runFail("build", "x")
4466+
tg.grepStderr("invalid flag in #cgo CFLAGS: @foo", "did not reject @foo flag")
4467+
4468+
tg.tempFile("src/x/y.go", `package x
4469+
// #cgo CFLAGS: -D
4470+
import "C"
4471+
`)
4472+
tg.runFail("build", "x")
4473+
tg.grepStderr("invalid flag in #cgo CFLAGS: -D without argument", "did not reject trailing -I flag")
4474+
4475+
// Note that -I @foo is allowed because we rewrite it into -I /path/to/src/@foo
4476+
// before the check is applied. There's no such rewrite for -D.
4477+
4478+
tg.tempFile("src/x/y.go", `package x
4479+
// #cgo CFLAGS: -D @foo
4480+
import "C"
4481+
`)
4482+
tg.runFail("build", "x")
4483+
tg.grepStderr("invalid flag in #cgo CFLAGS: -D @foo", "did not reject -D @foo flag")
4484+
4485+
tg.tempFile("src/x/y.go", `package x
4486+
// #cgo CFLAGS: -D@foo
4487+
import "C"
4488+
`)
4489+
tg.runFail("build", "x")
4490+
tg.grepStderr("invalid flag in #cgo CFLAGS: -D@foo", "did not reject -D@foo flag")
4491+
4492+
tg.setenv("CGO_CFLAGS", "-D@foo")
4493+
tg.tempFile("src/x/y.go", `package x
4494+
import "C"
4495+
`)
4496+
tg.run("build", "-n", "x")
4497+
tg.grepStderr("-D@foo", "did not find -D@foo in commands")
4498+
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,12 @@ func findEnv(env []cfg.EnvVar, name string) string {
101101
func ExtraEnvVars() []cfg.EnvVar {
102102
var b work.Builder
103103
b.Init()
104-
cppflags, cflags, cxxflags, fflags, ldflags := b.CFlags(&load.Package{})
104+
cppflags, cflags, cxxflags, fflags, ldflags, err := b.CFlags(&load.Package{})
105+
if err != nil {
106+
// Should not happen - b.CFlags was given an empty package.
107+
fmt.Fprintf(os.Stderr, "go: invalid cflags: %v\n", err)
108+
return nil
109+
}
105110
return []cfg.EnvVar{
106111
{Name: "CGO_CFLAGS", Value: strings.Join(cflags, " ")},
107112
{Name: "CGO_CPPFLAGS", Value: strings.Join(cppflags, " ")},

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

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -481,17 +481,26 @@ Environment variables for use with cgo:
481481
CGO_CFLAGS
482482
Flags that cgo will pass to the compiler when compiling
483483
C code.
484-
CGO_CPPFLAGS
485-
Flags that cgo will pass to the compiler when compiling
486-
C or C++ code.
487-
CGO_CXXFLAGS
488-
Flags that cgo will pass to the compiler when compiling
489-
C++ code.
490-
CGO_FFLAGS
491-
Flags that cgo will pass to the compiler when compiling
492-
Fortran code.
493-
CGO_LDFLAGS
494-
Flags that cgo will pass to the compiler when linking.
484+
CGO_CFLAGS_ALLOW
485+
A regular expression specifying additional flags to allow
486+
to appear in #cgo CFLAGS source code directives.
487+
Does not apply to the CGO_CFLAGS environment variable.
488+
CGO_CFLAGS_DISALLOW
489+
A regular expression specifying flags that must be disallowed
490+
from appearing in #cgo CFLAGS source code directives.
491+
Does not apply to the CGO_CFLAGS environment variable.
492+
CGO_CPPFLAGS, CGO_CPPFLAGS_ALLOW, CGO_CPPFLAGS_DISALLOW
493+
Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
494+
but for the C preprocessor.
495+
CGO_CXXFLAGS, CGO_CXXFLAGS_ALLOW, CGO_CXXFLAGS_DISALLOW
496+
Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
497+
but for the C++ compiler.
498+
CGO_FFLAGS, CGO_FFLAGS_ALLOW, CGO_FFLAGS_DISALLOW
499+
Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
500+
but for the Fortran compiler.
501+
CGO_LDFLAGS, CGO_LDFLAGS_ALLOW, CGO_LDFLAGS_DISALLOW
502+
Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
503+
but for the linker.
495504
CXX
496505
The command to use to compile C++ code.
497506
PKG_CONFIG

0 commit comments

Comments
 (0)