diff options
Diffstat (limited to 'misc/cgo/testsanitizers')
27 files changed, 1659 insertions, 0 deletions
diff --git a/misc/cgo/testsanitizers/cc_test.go b/misc/cgo/testsanitizers/cc_test.go new file mode 100644 index 0000000..0192a66 --- /dev/null +++ b/misc/cgo/testsanitizers/cc_test.go @@ -0,0 +1,442 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// sanitizers_test checks the use of Go with sanitizers like msan, asan, etc. +// See https://github.com/google/sanitizers. +package sanitizers_test + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "regexp" + "strconv" + "strings" + "sync" + "syscall" + "testing" + "unicode" +) + +var overcommit struct { + sync.Once + value int + err error +} + +// requireOvercommit skips t if the kernel does not allow overcommit. +func requireOvercommit(t *testing.T) { + t.Helper() + + overcommit.Once.Do(func() { + var out []byte + out, overcommit.err = ioutil.ReadFile("/proc/sys/vm/overcommit_memory") + if overcommit.err != nil { + return + } + overcommit.value, overcommit.err = strconv.Atoi(string(bytes.TrimSpace(out))) + }) + + if overcommit.err != nil { + t.Skipf("couldn't determine vm.overcommit_memory (%v); assuming no overcommit", overcommit.err) + } + if overcommit.value == 2 { + t.Skip("vm.overcommit_memory=2") + } +} + +var env struct { + sync.Once + m map[string]string + err error +} + +// goEnv returns the output of $(go env) as a map. +func goEnv(key string) (string, error) { + env.Once.Do(func() { + var out []byte + out, env.err = exec.Command("go", "env", "-json").Output() + if env.err != nil { + return + } + + env.m = make(map[string]string) + env.err = json.Unmarshal(out, &env.m) + }) + if env.err != nil { + return "", env.err + } + + v, ok := env.m[key] + if !ok { + return "", fmt.Errorf("`go env`: no entry for %v", key) + } + return v, nil +} + +// replaceEnv sets the key environment variable to value in cmd. +func replaceEnv(cmd *exec.Cmd, key, value string) { + if cmd.Env == nil { + cmd.Env = os.Environ() + } + cmd.Env = append(cmd.Env, key+"="+value) +} + +// mustRun executes t and fails cmd with a well-formatted message if it fails. +func mustRun(t *testing.T, cmd *exec.Cmd) { + t.Helper() + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("%#q exited with %v\n%s", strings.Join(cmd.Args, " "), err, out) + } +} + +// cc returns a cmd that executes `$(go env CC) $(go env GOGCCFLAGS) $args`. +func cc(args ...string) (*exec.Cmd, error) { + CC, err := goEnv("CC") + if err != nil { + return nil, err + } + + GOGCCFLAGS, err := goEnv("GOGCCFLAGS") + if err != nil { + return nil, err + } + + // Split GOGCCFLAGS, respecting quoting. + // + // TODO(bcmills): This code also appears in + // misc/cgo/testcarchive/carchive_test.go, and perhaps ought to go in + // src/cmd/dist/test.go as well. Figure out where to put it so that it can be + // shared. + var flags []string + quote := '\000' + start := 0 + lastSpace := true + backslash := false + for i, c := range GOGCCFLAGS { + if quote == '\000' && unicode.IsSpace(c) { + if !lastSpace { + flags = append(flags, GOGCCFLAGS[start:i]) + lastSpace = true + } + } else { + if lastSpace { + start = i + lastSpace = false + } + if quote == '\000' && !backslash && (c == '"' || c == '\'') { + quote = c + backslash = false + } else if !backslash && quote == c { + quote = '\000' + } else if (quote == '\000' || quote == '"') && !backslash && c == '\\' { + backslash = true + } else { + backslash = false + } + } + } + if !lastSpace { + flags = append(flags, GOGCCFLAGS[start:]) + } + + cmd := exec.Command(CC, flags...) + cmd.Args = append(cmd.Args, args...) + return cmd, nil +} + +type version struct { + name string + major, minor int +} + +var compiler struct { + sync.Once + version + err error +} + +// compilerVersion detects the version of $(go env CC). +// +// It returns a non-nil error if the compiler matches a known version schema but +// the version could not be parsed, or if $(go env CC) could not be determined. +func compilerVersion() (version, error) { + compiler.Once.Do(func() { + compiler.err = func() error { + compiler.name = "unknown" + + cmd, err := cc("--version") + if err != nil { + return err + } + out, err := cmd.Output() + if err != nil { + // Compiler does not support "--version" flag: not Clang or GCC. + return nil + } + + var match [][]byte + if bytes.HasPrefix(out, []byte("gcc")) { + compiler.name = "gcc" + + cmd, err := cc("-dumpversion") + if err != nil { + return err + } + out, err := cmd.Output() + if err != nil { + // gcc, but does not support gcc's "-dumpversion" flag?! + return err + } + gccRE := regexp.MustCompile(`(\d+)\.(\d+)`) + match = gccRE.FindSubmatch(out) + } else { + clangRE := regexp.MustCompile(`clang version (\d+)\.(\d+)`) + if match = clangRE.FindSubmatch(out); len(match) > 0 { + compiler.name = "clang" + } + } + + if len(match) < 3 { + return nil // "unknown" + } + if compiler.major, err = strconv.Atoi(string(match[1])); err != nil { + return err + } + if compiler.minor, err = strconv.Atoi(string(match[2])); err != nil { + return err + } + return nil + }() + }) + return compiler.version, compiler.err +} + +type compilerCheck struct { + once sync.Once + err error + skip bool // If true, skip with err instead of failing with it. +} + +type config struct { + sanitizer string + + cFlags, ldFlags, goFlags []string + + sanitizerCheck, runtimeCheck compilerCheck +} + +var configs struct { + sync.Mutex + m map[string]*config +} + +// configure returns the configuration for the given sanitizer. +func configure(sanitizer string) *config { + configs.Lock() + defer configs.Unlock() + if c, ok := configs.m[sanitizer]; ok { + return c + } + + c := &config{ + sanitizer: sanitizer, + cFlags: []string{"-fsanitize=" + sanitizer}, + ldFlags: []string{"-fsanitize=" + sanitizer}, + } + + if testing.Verbose() { + c.goFlags = append(c.goFlags, "-x") + } + + switch sanitizer { + case "memory": + c.goFlags = append(c.goFlags, "-msan") + + case "thread": + c.goFlags = append(c.goFlags, "--installsuffix=tsan") + compiler, _ := compilerVersion() + if compiler.name == "gcc" { + c.cFlags = append(c.cFlags, "-fPIC") + c.ldFlags = append(c.ldFlags, "-fPIC", "-static-libtsan") + } + + default: + panic(fmt.Sprintf("unrecognized sanitizer: %q", sanitizer)) + } + + if configs.m == nil { + configs.m = make(map[string]*config) + } + configs.m[sanitizer] = c + return c +} + +// goCmd returns a Cmd that executes "go $subcommand $args" with appropriate +// additional flags and environment. +func (c *config) goCmd(subcommand string, args ...string) *exec.Cmd { + cmd := exec.Command("go", subcommand) + cmd.Args = append(cmd.Args, c.goFlags...) + cmd.Args = append(cmd.Args, args...) + replaceEnv(cmd, "CGO_CFLAGS", strings.Join(c.cFlags, " ")) + replaceEnv(cmd, "CGO_LDFLAGS", strings.Join(c.ldFlags, " ")) + return cmd +} + +// skipIfCSanitizerBroken skips t if the C compiler does not produce working +// binaries as configured. +func (c *config) skipIfCSanitizerBroken(t *testing.T) { + check := &c.sanitizerCheck + check.once.Do(func() { + check.skip, check.err = c.checkCSanitizer() + }) + if check.err != nil { + t.Helper() + if check.skip { + t.Skip(check.err) + } + t.Fatal(check.err) + } +} + +var cMain = []byte(` +int main() { + return 0; +} +`) + +func (c *config) checkCSanitizer() (skip bool, err error) { + dir, err := ioutil.TempDir("", c.sanitizer) + if err != nil { + return false, fmt.Errorf("failed to create temp directory: %v", err) + } + defer os.RemoveAll(dir) + + src := filepath.Join(dir, "return0.c") + if err := ioutil.WriteFile(src, cMain, 0600); err != nil { + return false, fmt.Errorf("failed to write C source file: %v", err) + } + + dst := filepath.Join(dir, "return0") + cmd, err := cc(c.cFlags...) + if err != nil { + return false, err + } + cmd.Args = append(cmd.Args, c.ldFlags...) + cmd.Args = append(cmd.Args, "-o", dst, src) + out, err := cmd.CombinedOutput() + if err != nil { + if bytes.Contains(out, []byte("-fsanitize")) && + (bytes.Contains(out, []byte("unrecognized")) || + bytes.Contains(out, []byte("unsupported"))) { + return true, errors.New(string(out)) + } + return true, fmt.Errorf("%#q failed: %v\n%s", strings.Join(cmd.Args, " "), err, out) + } + + if out, err := exec.Command(dst).CombinedOutput(); err != nil { + if os.IsNotExist(err) { + return true, fmt.Errorf("%#q failed to produce executable: %v", strings.Join(cmd.Args, " "), err) + } + snippet := bytes.SplitN(out, []byte{'\n'}, 2)[0] + return true, fmt.Errorf("%#q generated broken executable: %v\n%s", strings.Join(cmd.Args, " "), err, snippet) + } + + return false, nil +} + +// skipIfRuntimeIncompatible skips t if the Go runtime is suspected not to work +// with cgo as configured. +func (c *config) skipIfRuntimeIncompatible(t *testing.T) { + check := &c.runtimeCheck + check.once.Do(func() { + check.skip, check.err = c.checkRuntime() + }) + if check.err != nil { + t.Helper() + if check.skip { + t.Skip(check.err) + } + t.Fatal(check.err) + } +} + +func (c *config) checkRuntime() (skip bool, err error) { + if c.sanitizer != "thread" { + return false, nil + } + + // libcgo.h sets CGO_TSAN if it detects TSAN support in the C compiler. + // Dump the preprocessor defines to check that works. + // (Sometimes it doesn't: see https://golang.org/issue/15983.) + cmd, err := cc(c.cFlags...) + if err != nil { + return false, err + } + cmd.Args = append(cmd.Args, "-dM", "-E", "../../../src/runtime/cgo/libcgo.h") + cmdStr := strings.Join(cmd.Args, " ") + out, err := cmd.CombinedOutput() + if err != nil { + return false, fmt.Errorf("%#q exited with %v\n%s", cmdStr, err, out) + } + if !bytes.Contains(out, []byte("#define CGO_TSAN")) { + return true, fmt.Errorf("%#q did not define CGO_TSAN", cmdStr) + } + return false, nil +} + +// srcPath returns the path to the given file relative to this test's source tree. +func srcPath(path string) string { + return filepath.Join("testdata", path) +} + +// A tempDir manages a temporary directory within a test. +type tempDir struct { + base string +} + +func (d *tempDir) RemoveAll(t *testing.T) { + t.Helper() + if d.base == "" { + return + } + if err := os.RemoveAll(d.base); err != nil { + t.Fatalf("Failed to remove temp dir: %v", err) + } +} + +func (d *tempDir) Join(name string) string { + return filepath.Join(d.base, name) +} + +func newTempDir(t *testing.T) *tempDir { + t.Helper() + dir, err := ioutil.TempDir("", filepath.Dir(t.Name())) + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + return &tempDir{base: dir} +} + +// hangProneCmd returns an exec.Cmd for a command that is likely to hang. +// +// If one of these tests hangs, the caller is likely to kill the test process +// using SIGINT, which will be sent to all of the processes in the test's group. +// Unfortunately, TSAN in particular is prone to dropping signals, so the SIGINT +// may terminate the test binary but leave the subprocess running. hangProneCmd +// configures subprocess to receive SIGKILL instead to ensure that it won't +// leak. +func hangProneCmd(name string, arg ...string) *exec.Cmd { + cmd := exec.Command(name, arg...) + cmd.SysProcAttr = &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGKILL, + } + return cmd +} diff --git a/misc/cgo/testsanitizers/cshared_test.go b/misc/cgo/testsanitizers/cshared_test.go new file mode 100644 index 0000000..56063ea --- /dev/null +++ b/misc/cgo/testsanitizers/cshared_test.go @@ -0,0 +1,74 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sanitizers_test + +import ( + "fmt" + "io/ioutil" + "strings" + "testing" +) + +func TestShared(t *testing.T) { + t.Parallel() + requireOvercommit(t) + + GOOS, err := goEnv("GOOS") + if err != nil { + t.Fatal(err) + } + libExt := "so" + if GOOS == "darwin" { + libExt = "dylib" + } + + cases := []struct { + src string + sanitizer string + }{ + { + src: "msan_shared.go", + sanitizer: "memory", + }, + { + src: "tsan_shared.go", + sanitizer: "thread", + }, + } + + for _, tc := range cases { + tc := tc + name := strings.TrimSuffix(tc.src, ".go") + t.Run(name, func(t *testing.T) { + t.Parallel() + config := configure(tc.sanitizer) + config.skipIfCSanitizerBroken(t) + + dir := newTempDir(t) + defer dir.RemoveAll(t) + + lib := dir.Join(fmt.Sprintf("lib%s.%s", name, libExt)) + mustRun(t, config.goCmd("build", "-buildmode=c-shared", "-o", lib, srcPath(tc.src))) + + cSrc := dir.Join("main.c") + if err := ioutil.WriteFile(cSrc, cMain, 0600); err != nil { + t.Fatalf("failed to write C source file: %v", err) + } + + dstBin := dir.Join(name) + cmd, err := cc(config.cFlags...) + if err != nil { + t.Fatal(err) + } + cmd.Args = append(cmd.Args, config.ldFlags...) + cmd.Args = append(cmd.Args, "-o", dstBin, cSrc, lib) + mustRun(t, cmd) + + cmd = hangProneCmd(dstBin) + replaceEnv(cmd, "LD_LIBRARY_PATH", ".") + mustRun(t, cmd) + }) + } +} diff --git a/misc/cgo/testsanitizers/msan_test.go b/misc/cgo/testsanitizers/msan_test.go new file mode 100644 index 0000000..5e2f975 --- /dev/null +++ b/misc/cgo/testsanitizers/msan_test.go @@ -0,0 +1,57 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sanitizers_test + +import ( + "strings" + "testing" +) + +func TestMSAN(t *testing.T) { + t.Parallel() + requireOvercommit(t) + config := configure("memory") + config.skipIfCSanitizerBroken(t) + + mustRun(t, config.goCmd("build", "std")) + + cases := []struct { + src string + wantErr bool + }{ + {src: "msan.go"}, + {src: "msan2.go"}, + {src: "msan2_cmsan.go"}, + {src: "msan3.go"}, + {src: "msan4.go"}, + {src: "msan5.go"}, + {src: "msan6.go"}, + {src: "msan7.go"}, + {src: "msan_fail.go", wantErr: true}, + } + for _, tc := range cases { + tc := tc + name := strings.TrimSuffix(tc.src, ".go") + t.Run(name, func(t *testing.T) { + t.Parallel() + + dir := newTempDir(t) + defer dir.RemoveAll(t) + + outPath := dir.Join(name) + mustRun(t, config.goCmd("build", "-o", outPath, srcPath(tc.src))) + + cmd := hangProneCmd(outPath) + if tc.wantErr { + out, err := cmd.CombinedOutput() + if err != nil { + return + } + t.Fatalf("%#q exited without error; want MSAN failure\n%s", strings.Join(cmd.Args, " "), out) + } + mustRun(t, cmd) + }) + } +} diff --git a/misc/cgo/testsanitizers/testdata/msan.go b/misc/cgo/testsanitizers/testdata/msan.go new file mode 100644 index 0000000..7915fa8 --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/msan.go @@ -0,0 +1,35 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +/* +#include <stdint.h> + +void f(int32_t *p, int n) { + int i; + + for (i = 0; i < n; i++) { + p[i] = (int32_t)i; + } +} +*/ +import "C" + +import ( + "fmt" + "os" + "unsafe" +) + +func main() { + a := make([]int32, 10) + C.f((*C.int32_t)(unsafe.Pointer(&a[0])), C.int(len(a))) + for i, v := range a { + if i != int(v) { + fmt.Println("bad %d: %v\n", i, a) + os.Exit(1) + } + } +} diff --git a/misc/cgo/testsanitizers/testdata/msan2.go b/misc/cgo/testsanitizers/testdata/msan2.go new file mode 100644 index 0000000..6690cb0 --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/msan2.go @@ -0,0 +1,35 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +/* +#include <string.h> +#include <stdint.h> +#include <stdlib.h> + +void f(int32_t *p, int n) { + int32_t * volatile q = (int32_t *)malloc(sizeof(int32_t) * n); + memcpy(p, q, n * sizeof(*p)); + free(q); +} + +void g(int32_t *p, int n) { + if (p[4] != 1) { + abort(); + } +} +*/ +import "C" + +import ( + "unsafe" +) + +func main() { + a := make([]int32, 10) + C.f((*C.int32_t)(unsafe.Pointer(&a[0])), C.int(len(a))) + a[4] = 1 + C.g((*C.int32_t)(unsafe.Pointer(&a[0])), C.int(len(a))) +} diff --git a/misc/cgo/testsanitizers/testdata/msan2_cmsan.go b/misc/cgo/testsanitizers/testdata/msan2_cmsan.go new file mode 100644 index 0000000..8fdaea9 --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/msan2_cmsan.go @@ -0,0 +1,38 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +/* +#cgo LDFLAGS: -fsanitize=memory +#cgo CPPFLAGS: -fsanitize=memory + +#include <string.h> +#include <stdint.h> +#include <stdlib.h> + +void f(int32_t *p, int n) { + int32_t * volatile q = (int32_t *)malloc(sizeof(int32_t) * n); + memcpy(p, q, n * sizeof(*p)); + free(q); +} + +void g(int32_t *p, int n) { + if (p[4] != 1) { + abort(); + } +} +*/ +import "C" + +import ( + "unsafe" +) + +func main() { + a := make([]int32, 10) + C.f((*C.int32_t)(unsafe.Pointer(&a[0])), C.int(len(a))) + a[4] = 1 + C.g((*C.int32_t)(unsafe.Pointer(&a[0])), C.int(len(a))) +} diff --git a/misc/cgo/testsanitizers/testdata/msan3.go b/misc/cgo/testsanitizers/testdata/msan3.go new file mode 100644 index 0000000..61a9c29 --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/msan3.go @@ -0,0 +1,33 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +/* +extern int *GoFn(int *); + +// Yes, you can have definitions if you use //export, as long as they are weak. +int f(void) __attribute__ ((weak)); + +int f() { + int i; + int *p = GoFn(&i); + if (*p != 12345) + return 0; + return 1; +} +*/ +import "C" + +//export GoFn +func GoFn(p *C.int) *C.int { + *p = C.int(12345) + return p +} + +func main() { + if r := C.f(); r != 1 { + panic(r) + } +} diff --git a/misc/cgo/testsanitizers/testdata/msan4.go b/misc/cgo/testsanitizers/testdata/msan4.go new file mode 100644 index 0000000..6c91ff5 --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/msan4.go @@ -0,0 +1,50 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// The memory profiler can call copy from a slice on the system stack, +// which msan used to think meant a reference to uninitialized memory. + +/* +#include <time.h> +#include <unistd.h> + +extern void Nop(char*); + +// Use weak as a hack to permit defining a function even though we use export. +void poison() __attribute__ ((weak)); + +// Poison the stack. +void poison() { + char a[1024]; + Nop(&a[0]); +} + +*/ +import "C" + +import ( + "runtime" +) + +func main() { + runtime.MemProfileRate = 1 + start(100) +} + +func start(i int) { + if i == 0 { + return + } + C.poison() + // Tie up a thread. + // We won't actually wait for this sleep to complete. + go func() { C.sleep(1) }() + start(i - 1) +} + +//export Nop +func Nop(*C.char) { +} diff --git a/misc/cgo/testsanitizers/testdata/msan5.go b/misc/cgo/testsanitizers/testdata/msan5.go new file mode 100644 index 0000000..f1479eb --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/msan5.go @@ -0,0 +1,57 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// Using reflect to set a value was not seen by msan. + +/* +#include <stdlib.h> + +extern void Go1(int*); +extern void Go2(char*); + +// Use weak as a hack to permit defining a function even though we use export. +void C1() __attribute__ ((weak)); +void C2() __attribute__ ((weak)); + +void C1() { + int i; + Go1(&i); + if (i != 42) { + abort(); + } +} + +void C2() { + char a[2]; + a[1] = 42; + Go2(a); + if (a[0] != 42) { + abort(); + } +} +*/ +import "C" + +import ( + "reflect" + "unsafe" +) + +//export Go1 +func Go1(p *C.int) { + reflect.ValueOf(p).Elem().Set(reflect.ValueOf(C.int(42))) +} + +//export Go2 +func Go2(p *C.char) { + a := (*[2]byte)(unsafe.Pointer(p)) + reflect.Copy(reflect.ValueOf(a[:1]), reflect.ValueOf(a[1:])) +} + +func main() { + C.C1() + C.C2() +} diff --git a/misc/cgo/testsanitizers/testdata/msan6.go b/misc/cgo/testsanitizers/testdata/msan6.go new file mode 100644 index 0000000..003989c --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/msan6.go @@ -0,0 +1,72 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// A C function returning a value on the Go stack could leave the Go +// stack marked as uninitialized, potentially causing a later error +// when the stack is used for something else. Issue 26209. + +/* +#cgo LDFLAGS: -fsanitize=memory +#cgo CPPFLAGS: -fsanitize=memory + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +typedef struct { + uintptr_t a[20]; +} S; + +S f() { + S *p; + + p = (S *)(malloc(sizeof(S))); + p->a[0] = 0; + return *p; +} +*/ +import "C" + +// allocateStack extends the stack so that stack copying doesn't +// confuse the msan data structures. +//go:noinline +func allocateStack(i int) int { + if i == 0 { + return i + } + return allocateStack(i - 1) +} + +// F1 marks a chunk of stack as uninitialized. +// C.f returns an uninitialized struct on the stack, so msan will mark +// the stack as uninitialized. +//go:noinline +func F1() uintptr { + s := C.f() + return uintptr(s.a[0]) +} + +// F2 allocates a struct on the stack and converts it to an empty interface, +// which will call msanread and see that the data appears uninitialized. +//go:noinline +func F2() interface{} { + return C.S{} +} + +func poisonStack(i int) int { + if i == 0 { + return int(F1()) + } + F1() + r := poisonStack(i - 1) + F2() + return r +} + +func main() { + allocateStack(16384) + poisonStack(128) +} diff --git a/misc/cgo/testsanitizers/testdata/msan7.go b/misc/cgo/testsanitizers/testdata/msan7.go new file mode 100644 index 0000000..2f29fd2 --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/msan7.go @@ -0,0 +1,38 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// Test passing C struct to exported Go function. + +/* +#include <stdint.h> +#include <stdlib.h> + +// T is a C struct with alignment padding after b. +// The padding bytes are not considered initialized by MSAN. +// It is big enough to be passed on stack in C ABI (and least +// on AMD64). +typedef struct { char b; uintptr_t x, y; } T; + +extern void F(T); + +// Use weak as a hack to permit defining a function even though we use export. +void CF(int x) __attribute__ ((weak)); +void CF(int x) { + T *t = malloc(sizeof(T)); + t->b = (char)x; + t->x = x; + t->y = x; + F(*t); +} +*/ +import "C" + +//export F +func F(t C.T) { println(t.b, t.x, t.y) } + +func main() { + C.CF(C.int(0)) +} diff --git a/misc/cgo/testsanitizers/testdata/msan_fail.go b/misc/cgo/testsanitizers/testdata/msan_fail.go new file mode 100644 index 0000000..4c8dab3 --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/msan_fail.go @@ -0,0 +1,36 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +/* +#include <string.h> +#include <stdint.h> +#include <stdlib.h> + +void f(int32_t *p, int n) { + int32_t * volatile q = (int32_t *)malloc(sizeof(int32_t) * n); + memcpy(p, q, n * sizeof(*p)); + free(q); +} + +void g(int32_t *p, int n) { + if (p[4] != 1) { + // We shouldn't get here; msan should stop us first. + exit(0); + } +} +*/ +import "C" + +import ( + "unsafe" +) + +func main() { + a := make([]int32, 10) + C.f((*C.int32_t)(unsafe.Pointer(&a[0])), C.int(len(a))) + a[3] = 1 + C.g((*C.int32_t)(unsafe.Pointer(&a[0])), C.int(len(a))) +} diff --git a/misc/cgo/testsanitizers/testdata/msan_shared.go b/misc/cgo/testsanitizers/testdata/msan_shared.go new file mode 100644 index 0000000..966947c --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/msan_shared.go @@ -0,0 +1,12 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This program segfaulted during libpreinit when built with -msan: +// http://golang.org/issue/18707 + +package main + +import "C" + +func main() {} diff --git a/misc/cgo/testsanitizers/testdata/tsan.go b/misc/cgo/testsanitizers/testdata/tsan.go new file mode 100644 index 0000000..6c377a7 --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/tsan.go @@ -0,0 +1,44 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// This program produced false race reports when run under the C/C++ +// ThreadSanitizer, as it did not understand the synchronization in +// the Go code. + +/* +#cgo CFLAGS: -fsanitize=thread +#cgo LDFLAGS: -fsanitize=thread + +int val; + +int getVal() { + return val; +} + +void setVal(int i) { + val = i; +} +*/ +import "C" + +import ( + "runtime" +) + +func main() { + runtime.LockOSThread() + C.setVal(1) + c := make(chan bool) + go func() { + runtime.LockOSThread() + C.setVal(2) + c <- true + }() + <-c + if v := C.getVal(); v != 2 { + panic(v) + } +} diff --git a/misc/cgo/testsanitizers/testdata/tsan10.go b/misc/cgo/testsanitizers/testdata/tsan10.go new file mode 100644 index 0000000..a40f245 --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/tsan10.go @@ -0,0 +1,31 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// This program hung when run under the C/C++ ThreadSanitizer. +// TSAN defers asynchronous signals until the signaled thread calls into libc. +// Since the Go runtime makes direct futex syscalls, Go runtime threads could +// run for an arbitrarily long time without triggering the libc interceptors. +// See https://golang.org/issue/18717. + +import ( + "os" + "os/signal" + "syscall" +) + +/* +#cgo CFLAGS: -g -fsanitize=thread +#cgo LDFLAGS: -g -fsanitize=thread +*/ +import "C" + +func main() { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGUSR1) + defer signal.Stop(c) + syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) + <-c +} diff --git a/misc/cgo/testsanitizers/testdata/tsan11.go b/misc/cgo/testsanitizers/testdata/tsan11.go new file mode 100644 index 0000000..70ac9c8 --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/tsan11.go @@ -0,0 +1,55 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// This program hung when run under the C/C++ ThreadSanitizer. TSAN defers +// asynchronous signals until the signaled thread calls into libc. The runtime's +// sysmon goroutine idles itself using direct usleep syscalls, so it could +// run for an arbitrarily long time without triggering the libc interceptors. +// See https://golang.org/issue/18717. + +import ( + "os" + "os/signal" + "syscall" +) + +/* +#cgo CFLAGS: -g -fsanitize=thread +#cgo LDFLAGS: -g -fsanitize=thread + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static void raise_usr2(int signo) { + raise(SIGUSR2); +} + +static void register_handler(int signo) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_ONSTACK; + sa.sa_handler = raise_usr2; + + if (sigaction(SIGUSR1, &sa, NULL) != 0) { + perror("failed to register SIGUSR1 handler"); + exit(EXIT_FAILURE); + } +} +*/ +import "C" + +func main() { + ch := make(chan os.Signal) + signal.Notify(ch, syscall.SIGUSR2) + + C.register_handler(C.int(syscall.SIGUSR1)) + syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) + + <-ch +} diff --git a/misc/cgo/testsanitizers/testdata/tsan12.go b/misc/cgo/testsanitizers/testdata/tsan12.go new file mode 100644 index 0000000..3e767ee --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/tsan12.go @@ -0,0 +1,35 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// This program hung when run under the C/C++ ThreadSanitizer. TSAN installs a +// libc interceptor that writes signal handlers to a global variable within the +// TSAN runtime instead of making a sigaction system call. A bug in +// syscall.runtime_AfterForkInChild corrupted TSAN's signal forwarding table +// during calls to (*os/exec.Cmd).Run, causing the parent process to fail to +// invoke signal handlers. + +import ( + "fmt" + "os" + "os/exec" + "os/signal" + "syscall" +) + +import "C" + +func main() { + ch := make(chan os.Signal) + signal.Notify(ch, syscall.SIGUSR1) + + if err := exec.Command("true").Run(); err != nil { + fmt.Fprintf(os.Stderr, "Unexpected error from `true`: %v", err) + os.Exit(1) + } + + syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) + <-ch +} diff --git a/misc/cgo/testsanitizers/testdata/tsan2.go b/misc/cgo/testsanitizers/testdata/tsan2.go new file mode 100644 index 0000000..5018a19 --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/tsan2.go @@ -0,0 +1,55 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// This program produced false race reports when run under the C/C++ +// ThreadSanitizer, as it did not understand the synchronization in +// the Go code. + +/* +#cgo CFLAGS: -fsanitize=thread +#cgo LDFLAGS: -fsanitize=thread + +extern void GoRun(void); + +// Yes, you can have definitions if you use //export, as long as they are weak. + +int val __attribute__ ((weak)); + +int run(void) __attribute__ ((weak)); + +int run() { + val = 1; + GoRun(); + return val; +} + +void setVal(int) __attribute__ ((weak)); + +void setVal(int i) { + val = i; +} +*/ +import "C" + +import "runtime" + +//export GoRun +func GoRun() { + runtime.LockOSThread() + c := make(chan bool) + go func() { + runtime.LockOSThread() + C.setVal(2) + c <- true + }() + <-c +} + +func main() { + if v := C.run(); v != 2 { + panic(v) + } +} diff --git a/misc/cgo/testsanitizers/testdata/tsan3.go b/misc/cgo/testsanitizers/testdata/tsan3.go new file mode 100644 index 0000000..87f6c80 --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/tsan3.go @@ -0,0 +1,40 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// The stubs for the C functions read and write the same slot on the +// g0 stack when copying arguments in and out. + +/* +#cgo CFLAGS: -fsanitize=thread +#cgo LDFLAGS: -fsanitize=thread + +int Func1() { + return 0; +} + +void Func2(int x) { + (void)x; +} +*/ +import "C" + +func main() { + const N = 10000 + done := make(chan bool, N) + for i := 0; i < N; i++ { + go func() { + C.Func1() + done <- true + }() + go func() { + C.Func2(0) + done <- true + }() + } + for i := 0; i < 2*N; i++ { + <-done + } +} diff --git a/misc/cgo/testsanitizers/testdata/tsan4.go b/misc/cgo/testsanitizers/testdata/tsan4.go new file mode 100644 index 0000000..f0c76d8 --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/tsan4.go @@ -0,0 +1,34 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// Check that calls to C.malloc/C.free do not trigger TSAN false +// positive reports. + +// #cgo CFLAGS: -fsanitize=thread +// #cgo LDFLAGS: -fsanitize=thread +// #include <stdlib.h> +import "C" + +import ( + "runtime" + "sync" +) + +func main() { + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < 100; i++ { + p := C.malloc(C.size_t(i * 10)) + runtime.Gosched() + C.free(p) + } + }() + } + wg.Wait() +} diff --git a/misc/cgo/testsanitizers/testdata/tsan5.go b/misc/cgo/testsanitizers/testdata/tsan5.go new file mode 100644 index 0000000..1214a77 --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/tsan5.go @@ -0,0 +1,51 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// Check that calls to C.malloc/C.free do not collide with the calls +// made by the os/user package. + +// #cgo CFLAGS: -fsanitize=thread +// #cgo LDFLAGS: -fsanitize=thread +// #include <stdlib.h> +import "C" + +import ( + "fmt" + "os" + "os/user" + "runtime" + "sync" +) + +func main() { + u, err := user.Current() + if err != nil { + fmt.Fprintln(os.Stderr, err) + // Let the test pass. + os.Exit(0) + } + + var wg sync.WaitGroup + for i := 0; i < 20; i++ { + wg.Add(2) + go func() { + defer wg.Done() + for i := 0; i < 1000; i++ { + user.Lookup(u.Username) + runtime.Gosched() + } + }() + go func() { + defer wg.Done() + for i := 0; i < 1000; i++ { + p := C.malloc(C.size_t(len(u.Username) + 1)) + runtime.Gosched() + C.free(p) + } + }() + } + wg.Wait() +} diff --git a/misc/cgo/testsanitizers/testdata/tsan6.go b/misc/cgo/testsanitizers/testdata/tsan6.go new file mode 100644 index 0000000..c96f08d --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/tsan6.go @@ -0,0 +1,49 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// Check that writes to Go allocated memory, with Go synchronization, +// do not look like a race. + +/* +#cgo CFLAGS: -fsanitize=thread +#cgo LDFLAGS: -fsanitize=thread + +void f(char *p) { + *p = 1; +} +*/ +import "C" + +import ( + "runtime" + "sync" +) + +func main() { + var wg sync.WaitGroup + var mu sync.Mutex + c := make(chan []C.char, 100) + for i := 0; i < 10; i++ { + wg.Add(2) + go func() { + defer wg.Done() + for i := 0; i < 100; i++ { + c <- make([]C.char, 4096) + runtime.Gosched() + } + }() + go func() { + defer wg.Done() + for i := 0; i < 100; i++ { + p := &(<-c)[0] + mu.Lock() + C.f(p) + mu.Unlock() + } + }() + } + wg.Wait() +} diff --git a/misc/cgo/testsanitizers/testdata/tsan7.go b/misc/cgo/testsanitizers/testdata/tsan7.go new file mode 100644 index 0000000..2fb9e45 --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/tsan7.go @@ -0,0 +1,40 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// Setting an environment variable in a cgo program changes the C +// environment. Test that this does not confuse the race detector. + +/* +#cgo CFLAGS: -fsanitize=thread +#cgo LDFLAGS: -fsanitize=thread +*/ +import "C" + +import ( + "fmt" + "os" + "sync" + "time" +) + +func main() { + var wg sync.WaitGroup + var mu sync.Mutex + f := func() { + defer wg.Done() + for i := 0; i < 100; i++ { + time.Sleep(time.Microsecond) + mu.Lock() + s := fmt.Sprint(i) + os.Setenv("TSAN_TEST"+s, s) + mu.Unlock() + } + } + wg.Add(2) + go f() + go f() + wg.Wait() +} diff --git a/misc/cgo/testsanitizers/testdata/tsan8.go b/misc/cgo/testsanitizers/testdata/tsan8.go new file mode 100644 index 0000000..88d82a6 --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/tsan8.go @@ -0,0 +1,60 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// This program failed when run under the C/C++ ThreadSanitizer. The TSAN +// sigaction function interceptor returned SIG_DFL instead of the Go runtime's +// handler in registerSegvForwarder. + +/* +#cgo CFLAGS: -fsanitize=thread +#cgo LDFLAGS: -fsanitize=thread + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +struct sigaction prev_sa; + +void forwardSignal(int signo, siginfo_t *info, void *context) { + // One of sa_sigaction and/or sa_handler + if ((prev_sa.sa_flags&SA_SIGINFO) != 0) { + prev_sa.sa_sigaction(signo, info, context); + return; + } + if (prev_sa.sa_handler != SIG_IGN && prev_sa.sa_handler != SIG_DFL) { + prev_sa.sa_handler(signo); + return; + } + + fprintf(stderr, "No Go handler to forward to!\n"); + abort(); +} + +void registerSegvFowarder() { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; + sa.sa_sigaction = forwardSignal; + + if (sigaction(SIGSEGV, &sa, &prev_sa) != 0) { + perror("failed to register SEGV forwarder"); + exit(EXIT_FAILURE); + } +} +*/ +import "C" + +func main() { + C.registerSegvFowarder() + + defer func() { + recover() + }() + var nilp *int + *nilp = 42 +} diff --git a/misc/cgo/testsanitizers/testdata/tsan9.go b/misc/cgo/testsanitizers/testdata/tsan9.go new file mode 100644 index 0000000..f166d8b --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/tsan9.go @@ -0,0 +1,67 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// This program failed when run under the C/C++ ThreadSanitizer. The +// TSAN library was not keeping track of whether signals should be +// delivered on the alternate signal stack, and the Go signal handler +// was not preserving callee-saved registers from C callers. + +/* +#cgo CFLAGS: -g -fsanitize=thread +#cgo LDFLAGS: -g -fsanitize=thread + +#include <stdlib.h> +#include <sys/time.h> + +void spin() { + size_t n; + struct timeval tvstart, tvnow; + int diff; + void *prev = NULL, *cur; + + gettimeofday(&tvstart, NULL); + for (n = 0; n < 1<<20; n++) { + cur = malloc(n); + free(prev); + prev = cur; + + gettimeofday(&tvnow, NULL); + diff = (tvnow.tv_sec - tvstart.tv_sec) * 1000 * 1000 + (tvnow.tv_usec - tvstart.tv_usec); + + // Profile frequency is 100Hz so we should definitely + // get a signal in 50 milliseconds. + if (diff > 50 * 1000) { + break; + } + } + + free(prev); +} +*/ +import "C" + +import ( + "io/ioutil" + "runtime/pprof" + "time" +) + +func goSpin() { + start := time.Now() + for n := 0; n < 1<<20; n++ { + _ = make([]byte, n) + if time.Since(start) > 50*time.Millisecond { + break + } + } +} + +func main() { + pprof.StartCPUProfile(ioutil.Discard) + go C.spin() + goSpin() + pprof.StopCPUProfile() +} diff --git a/misc/cgo/testsanitizers/testdata/tsan_shared.go b/misc/cgo/testsanitizers/testdata/tsan_shared.go new file mode 100644 index 0000000..55ff67e --- /dev/null +++ b/misc/cgo/testsanitizers/testdata/tsan_shared.go @@ -0,0 +1,63 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// This program failed with SIGSEGV when run under the C/C++ ThreadSanitizer. +// The Go runtime had re-registered the C handler with the wrong flags due to a +// typo, resulting in null pointers being passed for the info and context +// parameters to the handler. + +/* +#cgo CFLAGS: -fsanitize=thread +#cgo LDFLAGS: -fsanitize=thread + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ucontext.h> + +void check_params(int signo, siginfo_t *info, void *context) { + ucontext_t* uc = (ucontext_t*)(context); + + if (info->si_signo != signo) { + fprintf(stderr, "info->si_signo does not match signo.\n"); + abort(); + } + + if (uc->uc_stack.ss_size == 0) { + fprintf(stderr, "uc_stack has size 0.\n"); + abort(); + } +} + + +// Set up the signal handler in a high priority constructor, so +// that it is installed before the Go code starts. + +static void register_handler(void) __attribute__ ((constructor (200))); + +static void register_handler() { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = check_params; + + if (sigaction(SIGUSR1, &sa, NULL) != 0) { + perror("failed to register SIGUSR1 handler"); + exit(EXIT_FAILURE); + } +} +*/ +import "C" + +import "syscall" + +func init() { + C.raise(C.int(syscall.SIGUSR1)) +} + +func main() {} diff --git a/misc/cgo/testsanitizers/tsan_test.go b/misc/cgo/testsanitizers/tsan_test.go new file mode 100644 index 0000000..ec4e003 --- /dev/null +++ b/misc/cgo/testsanitizers/tsan_test.go @@ -0,0 +1,56 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sanitizers_test + +import ( + "strings" + "testing" +) + +func TestTSAN(t *testing.T) { + t.Parallel() + requireOvercommit(t) + config := configure("thread") + config.skipIfCSanitizerBroken(t) + + mustRun(t, config.goCmd("build", "std")) + + cases := []struct { + src string + needsRuntime bool + }{ + {src: "tsan.go"}, + {src: "tsan2.go"}, + {src: "tsan3.go"}, + {src: "tsan4.go"}, + {src: "tsan5.go", needsRuntime: true}, + {src: "tsan6.go", needsRuntime: true}, + {src: "tsan7.go", needsRuntime: true}, + {src: "tsan8.go"}, + {src: "tsan9.go"}, + {src: "tsan10.go", needsRuntime: true}, + {src: "tsan11.go", needsRuntime: true}, + {src: "tsan12.go", needsRuntime: true}, + } + for _, tc := range cases { + tc := tc + name := strings.TrimSuffix(tc.src, ".go") + t.Run(name, func(t *testing.T) { + t.Parallel() + + dir := newTempDir(t) + defer dir.RemoveAll(t) + + outPath := dir.Join(name) + mustRun(t, config.goCmd("build", "-o", outPath, srcPath(tc.src))) + + cmd := hangProneCmd(outPath) + if tc.needsRuntime { + config.skipIfRuntimeIncompatible(t) + } + mustRun(t, cmd) + }) + } +} |