Skip to content

Commit

Permalink
cmd/go: accept only limited compiler and linker flags in #cgo directives
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
rsc authored and Russ Cox committed Feb 7, 2018
1 parent b2d3d6e commit 1dcb583
Show file tree
Hide file tree
Showing 12 changed files with 780 additions and 60 deletions.
2 changes: 1 addition & 1 deletion misc/cgo/errors/src/err1.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
package main

/*
#cgo LDFLAGS: -c
#cgo LDFLAGS: -L/nonexist
void test() {
xxx; // ERROR HERE
Expand Down
16 changes: 13 additions & 3 deletions src/cmd/cgo/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ For example:
// #include <png.h>
import "C"
Alternatively, CPPFLAGS and LDFLAGS may be obtained via the pkg-config
tool using a '#cgo pkg-config:' directive followed by the package names.
Alternatively, CPPFLAGS and LDFLAGS may be obtained via the pkg-config tool
using a '#cgo pkg-config:' directive followed by the package names.
For example:
// #cgo pkg-config: png cairo
Expand All @@ -55,11 +55,21 @@ For example:
The default pkg-config tool may be changed by setting the PKG_CONFIG environment variable.
For security reasons, only a limited set of flags are allowed, notably -D, -I, and -l.
To allow additional flags, set CGO_CFLAGS_ALLOW to a regular expression
matching the new flags. To disallow flags that would otherwise be allowed,
set CGO_CFLAGS_DISALLOW to a regular expression matching arguments
that must be disallowed. In both cases the regular expression must match
a full argument: to allow -mfoo=bar, use CGO_CFLAGS_ALLOW='-mfoo.*',
not just CGO_CFLAGS_ALLOW='-mfoo'. Similarly named variables control
the allowed CPPFLAGS, CXXFLAGS, FFLAGS, and LDFLAGS.
When building, the CGO_CFLAGS, CGO_CPPFLAGS, CGO_CXXFLAGS, CGO_FFLAGS and
CGO_LDFLAGS environment variables are added to the flags derived from
these directives. Package-specific flags should be set using the
directives, not the environment variables, so that builds work in
unmodified environments.
unmodified environments. Flags obtained from environment variables
are not subject to the security limitations described above.
All the cgo CPPFLAGS and CFLAGS directives in a package are concatenated and
used to compile C files in that package. All the CPPFLAGS and CXXFLAGS
Expand Down
16 changes: 16 additions & 0 deletions src/cmd/compile/internal/gc/noder.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package gc
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
Expand Down Expand Up @@ -1346,6 +1347,11 @@ func (p *noder) pragma(pos src.Pos, text string) syntax.Pragma {
p.linknames = append(p.linknames, linkname{pos, f[1], f[2]})

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

// isCgoGeneratedFile reports whether pos is in a file
// generated by cgo, which is to say a file with name
// beginning with "_cgo_". Such files are allowed to
// contain cgo directives, and for security reasons
// (primarily misuse of linker flags), other files are not.
// See golang.org/issue/23672.
func isCgoGeneratedFile(pos src.Pos) bool {
return strings.HasPrefix(filepath.Base(filepath.Clean(pos.AbsFilename())), "_cgo_")
}

func mkname(sym *types.Sym) *Node {
n := oldname(sym)
if n.Name != nil && n.Name.Pack != nil {
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/dist/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,7 @@ func runInstall(dir string, ch chan struct{}) {
} else {
archive = b
}
compile := []string{pathf("%s/compile", tooldir), "-pack", "-o", b, "-p", pkg}
compile := []string{pathf("%s/compile", tooldir), "-std", "-pack", "-o", b, "-p", pkg}
if gogcflags != "" {
compile = append(compile, strings.Fields(gogcflags)...)
}
Expand Down
31 changes: 20 additions & 11 deletions src/cmd/go/alldocs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1227,17 +1227,26 @@
// CGO_CFLAGS
// Flags that cgo will pass to the compiler when compiling
// C code.
// CGO_CPPFLAGS
// Flags that cgo will pass to the compiler when compiling
// C or C++ code.
// CGO_CXXFLAGS
// Flags that cgo will pass to the compiler when compiling
// C++ code.
// CGO_FFLAGS
// Flags that cgo will pass to the compiler when compiling
// Fortran code.
// CGO_LDFLAGS
// Flags that cgo will pass to the compiler when linking.
// CGO_CFLAGS_ALLOW
// A regular expression specifying additional flags to allow
// to appear in #cgo CFLAGS source code directives.
// Does not apply to the CGO_CFLAGS environment variable.
// CGO_CFLAGS_DISALLOW
// A regular expression specifying flags that must be disallowed
// from appearing in #cgo CFLAGS source code directives.
// Does not apply to the CGO_CFLAGS environment variable.
// CGO_CPPFLAGS, CGO_CPPFLAGS_ALLOW, CGO_CPPFLAGS_DISALLOW
// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
// but for the C preprocessor.
// CGO_CXXFLAGS, CGO_CXXFLAGS_ALLOW, CGO_CXXFLAGS_DISALLOW
// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
// but for the C++ compiler.
// CGO_FFLAGS, CGO_FFLAGS_ALLOW, CGO_FFLAGS_DISALLOW
// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
// but for the Fortran compiler.
// CGO_LDFLAGS, CGO_LDFLAGS_ALLOW, CGO_LDFLAGS_DISALLOW
// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
// but for the linker.
// CXX
// The command to use to compile C++ code.
// PKG_CONFIG
Expand Down
151 changes: 150 additions & 1 deletion src/cmd/go/go_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2789,7 +2789,7 @@ func TestCgoHandlesWlORIGIN(t *testing.T) {
defer tg.cleanup()
tg.parallel()
tg.tempFile("src/origin/origin.go", `package origin
// #cgo !darwin LDFLAGS: -Wl,-rpath -Wl,$ORIGIN
// #cgo !darwin LDFLAGS: -Wl,-rpath,$ORIGIN
// void f(void) {}
import "C"
func f() { C.f() }`)
Expand Down Expand Up @@ -5739,3 +5739,152 @@ func TestAtomicCoverpkgAll(t *testing.T) {
tg.run("test", "-coverpkg=all", "-race", "x")
}
}

func TestBadCommandLines(t *testing.T) {
tg := testgo(t)
defer tg.cleanup()

tg.tempFile("src/x/x.go", "package x\n")
tg.setenv("GOPATH", tg.path("."))

tg.run("build", "x")

tg.tempFile("src/x/@y.go", "package x\n")
tg.runFail("build", "x")
tg.grepStderr("invalid input file name \"@y.go\"", "did not reject @y.go")
tg.must(os.Remove(tg.path("src/x/@y.go")))

tg.tempFile("src/x/-y.go", "package x\n")
tg.runFail("build", "x")
tg.grepStderr("invalid input file name \"-y.go\"", "did not reject -y.go")
tg.must(os.Remove(tg.path("src/x/-y.go")))

tg.runFail("build", "-gcflags=all=@x", "x")
tg.grepStderr("invalid command-line argument @x in command", "did not reject @x during exec")

tg.tempFile("src/@x/x.go", "package x\n")
tg.setenv("GOPATH", tg.path("."))
tg.runFail("build", "@x")
tg.grepStderr("invalid input directory name \"@x\"", "did not reject @x directory")

tg.tempFile("src/@x/y/y.go", "package y\n")
tg.setenv("GOPATH", tg.path("."))
tg.runFail("build", "@x/y")
tg.grepStderr("invalid import path \"@x/y\"", "did not reject @x/y import path")

tg.tempFile("src/-x/x.go", "package x\n")
tg.setenv("GOPATH", tg.path("."))
tg.runFail("build", "--", "-x")
tg.grepStderr("invalid input directory name \"-x\"", "did not reject -x directory")

tg.tempFile("src/-x/y/y.go", "package y\n")
tg.setenv("GOPATH", tg.path("."))
tg.runFail("build", "--", "-x/y")
tg.grepStderr("invalid import path \"-x/y\"", "did not reject -x/y import path")
}

func TestBadCgoDirectives(t *testing.T) {
if !canCgo {
t.Skip("no cgo")
}
tg := testgo(t)
defer tg.cleanup()

tg.tempFile("src/x/x.go", "package x\n")
tg.setenv("GOPATH", tg.path("."))

tg.tempFile("src/x/x.go", `package x
//go:cgo_ldflag "-fplugin=foo.so"
import "C"
`)
tg.runFail("build", "x")
tg.grepStderr("//go:cgo_ldflag .* only allowed in cgo-generated code", "did not reject //go:cgo_ldflag directive")

tg.must(os.Remove(tg.path("src/x/x.go")))
tg.runFail("build", "x")
tg.grepStderr("no Go files", "did not report missing source code")
tg.tempFile("src/x/_cgo_yy.go", `package x
//go:cgo_ldflag "-fplugin=foo.so"
import "C"
`)
tg.runFail("build", "x")
tg.grepStderr("no Go files", "did not report missing source code") // _* files are ignored...

tg.runFail("build", tg.path("src/x/_cgo_yy.go")) // ... but if forced, the comment is rejected
// Actually, today there is a separate issue that _ files named
// on the command-line are ignored. Once that is fixed,
// we want to see the cgo_ldflag error.
tg.grepStderr("//go:cgo_ldflag only allowed in cgo-generated code|no Go files", "did not reject //go:cgo_ldflag directive")
tg.must(os.Remove(tg.path("src/x/_cgo_yy.go")))

tg.tempFile("src/x/x.go", "package x\n")
tg.tempFile("src/x/y.go", `package x
// #cgo CFLAGS: -fplugin=foo.so
import "C"
`)
tg.runFail("build", "x")
tg.grepStderr("invalid flag in #cgo CFLAGS: -fplugin=foo.so", "did not reject -fplugin")

tg.tempFile("src/x/y.go", `package x
// #cgo CFLAGS: -Ibar -fplugin=foo.so
import "C"
`)
tg.runFail("build", "x")
tg.grepStderr("invalid flag in #cgo CFLAGS: -fplugin=foo.so", "did not reject -fplugin")

tg.tempFile("src/x/y.go", `package x
// #cgo pkg-config: -foo
import "C"
`)
tg.runFail("build", "x")
tg.grepStderr("invalid pkg-config package name: -foo", "did not reject pkg-config: -foo")

tg.tempFile("src/x/y.go", `package x
// #cgo pkg-config: @foo
import "C"
`)
tg.runFail("build", "x")
tg.grepStderr("invalid pkg-config package name: @foo", "did not reject pkg-config: -foo")

tg.tempFile("src/x/y.go", `package x
// #cgo CFLAGS: @foo
import "C"
`)
tg.runFail("build", "x")
tg.grepStderr("invalid flag in #cgo CFLAGS: @foo", "did not reject @foo flag")

tg.tempFile("src/x/y.go", `package x
// #cgo CFLAGS: -D
import "C"
`)
tg.runFail("build", "x")
tg.grepStderr("invalid flag in #cgo CFLAGS: -D without argument", "did not reject trailing -I flag")

// Note that -I @foo is allowed because we rewrite it into -I /path/to/src/@foo
// before the check is applied. There's no such rewrite for -D.

tg.tempFile("src/x/y.go", `package x
// #cgo CFLAGS: -D @foo
import "C"
`)
tg.runFail("build", "x")
tg.grepStderr("invalid flag in #cgo CFLAGS: -D @foo", "did not reject -D @foo flag")

tg.tempFile("src/x/y.go", `package x
// #cgo CFLAGS: -D@foo
import "C"
`)
tg.runFail("build", "x")
tg.grepStderr("invalid flag in #cgo CFLAGS: -D@foo", "did not reject -D@foo flag")

tg.setenv("CGO_CFLAGS", "-D@foo")
tg.tempFile("src/x/y.go", `package x
import "C"
`)
tg.run("build", "-n", "x")
tg.grepStderr("-D@foo", "did not find -D@foo in commands")
}
7 changes: 6 additions & 1 deletion src/cmd/go/internal/envcmd/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,12 @@ func findEnv(env []cfg.EnvVar, name string) string {
func ExtraEnvVars() []cfg.EnvVar {
var b work.Builder
b.Init()
cppflags, cflags, cxxflags, fflags, ldflags := b.CFlags(&load.Package{})
cppflags, cflags, cxxflags, fflags, ldflags, err := b.CFlags(&load.Package{})
if err != nil {
// Should not happen - b.CFlags was given an empty package.
fmt.Fprintf(os.Stderr, "go: invalid cflags: %v\n", err)
return nil
}
cmd := b.GccCmd(".", "")
return []cfg.EnvVar{
// Note: Update the switch in runEnv below when adding to this list.
Expand Down
31 changes: 20 additions & 11 deletions src/cmd/go/internal/help/helpdoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,17 +487,26 @@ Environment variables for use with cgo:
CGO_CFLAGS
Flags that cgo will pass to the compiler when compiling
C code.
CGO_CPPFLAGS
Flags that cgo will pass to the compiler when compiling
C or C++ code.
CGO_CXXFLAGS
Flags that cgo will pass to the compiler when compiling
C++ code.
CGO_FFLAGS
Flags that cgo will pass to the compiler when compiling
Fortran code.
CGO_LDFLAGS
Flags that cgo will pass to the compiler when linking.
CGO_CFLAGS_ALLOW
A regular expression specifying additional flags to allow
to appear in #cgo CFLAGS source code directives.
Does not apply to the CGO_CFLAGS environment variable.
CGO_CFLAGS_DISALLOW
A regular expression specifying flags that must be disallowed
from appearing in #cgo CFLAGS source code directives.
Does not apply to the CGO_CFLAGS environment variable.
CGO_CPPFLAGS, CGO_CPPFLAGS_ALLOW, CGO_CPPFLAGS_DISALLOW
Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
but for the C preprocessor.
CGO_CXXFLAGS, CGO_CXXFLAGS_ALLOW, CGO_CXXFLAGS_DISALLOW
Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
but for the C++ compiler.
CGO_FFLAGS, CGO_FFLAGS_ALLOW, CGO_FFLAGS_DISALLOW
Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
but for the Fortran compiler.
CGO_LDFLAGS, CGO_LDFLAGS_ALLOW, CGO_LDFLAGS_DISALLOW
Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
but for the linker.
CXX
The command to use to compile C++ code.
PKG_CONFIG
Expand Down
Loading

0 comments on commit 1dcb583

Please sign in to comment.