Add asan tests to check the use of Go with -asan option.
Currenly, the address sanitizer in Go only checks for error
memory access to heap objects.
TODO: Enable check for error memory access to global objects.
Updates #44853.
Change-Id: I83579f229f117b5684a369fc8f365f4dea140648
Reviewed-on: https://go-review.googlesource.com/c/go/+/298615
Trust: fannie zhang <Fannie.Zhang@arm.com>
Run-TryBot: fannie zhang <Fannie.Zhang@arm.com>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
--- /dev/null
+// Copyright 2021 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 TestASAN(t *testing.T) {
+ goos, err := goEnv("GOOS")
+ if err != nil {
+ t.Fatal(err)
+ }
+ goarch, err := goEnv("GOARCH")
+ if err != nil {
+ t.Fatal(err)
+ }
+ // The asan tests require support for the -asan option.
+ if !aSanSupported(goos, goarch) {
+ t.Skipf("skipping on %s/%s; -asan option is not supported.", goos, goarch)
+ }
+
+ t.Parallel()
+ requireOvercommit(t)
+ config := configure("address")
+ config.skipIfCSanitizerBroken(t)
+
+ mustRun(t, config.goCmd("build", "std"))
+
+ cases := []struct {
+ src string
+ memoryAccessError string
+ }{
+ {src: "asan1_fail.go", memoryAccessError: "heap-use-after-free"},
+ {src: "asan2_fail.go", memoryAccessError: "heap-buffer-overflow"},
+ {src: "asan3_fail.go", memoryAccessError: "use-after-poison"},
+ {src: "asan4_fail.go", memoryAccessError: "use-after-poison"},
+ {src: "asan_useAfterReturn.go"},
+ }
+ 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.memoryAccessError != "" {
+ out, err := cmd.CombinedOutput()
+ if err != nil && strings.Contains(string(out), tc.memoryAccessError) {
+ return
+ }
+ t.Fatalf("%#q exited without expected memory access error\n%s; got failure\n%s", strings.Join(cmd.Args, " "), tc.memoryAccessError, out)
+ }
+ mustRun(t, cmd)
+ })
+ }
+}
c.ldFlags = append(c.ldFlags, "-fPIC", "-static-libtsan")
}
+ case "address":
+ c.goFlags = append(c.goFlags, "-asan")
+
default:
panic(fmt.Sprintf("unrecognized sanitizer: %q", sanitizer))
}
return false
}
}
+
+// aSanSupported is a copy of the function cmd/internal/sys.ASanSupported,
+// because the internal pacakage can't be used here.
+func aSanSupported(goos, goarch string) bool {
+ switch goos {
+ case "linux":
+ return goarch == "amd64" || goarch == "arm64"
+ default:
+ return false
+ }
+}
--- /dev/null
+// Copyright 2021 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 <stdlib.h>
+#include <stdio.h>
+
+int *p;
+int* test() {
+ p = (int *)malloc(2 * sizeof(int));
+ free(p);
+ return p;
+}
+*/
+import "C"
+import "fmt"
+
+func main() {
+ // C passes Go an invalid pointer.
+ a := C.test()
+ // Use after free
+ *a = 2
+ // We shouldn't get here; asan should stop us first.
+ fmt.Println(*a)
+}
--- /dev/null
+// Copyright 2021 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 <stdlib.h>
+#include <stdio.h>
+
+int *p;
+int* f() {
+ int i;
+ p = (int *)malloc(5*sizeof(int));
+ for (i = 0; i < 5; i++) {
+ p[i] = i+10;
+ }
+ return p;
+}
+*/
+import "C"
+import (
+ "fmt"
+ "unsafe"
+)
+
+func main() {
+ a := C.f()
+ q5 := (*C.int)(unsafe.Add(unsafe.Pointer(a), 4*5))
+ // Access to C pointer out of bounds.
+ *q5 = 100
+ // We shouldn't get here; asan should stop us first.
+ fmt.Printf("q5: %d, %x\n", *q5, q5)
+}
--- /dev/null
+// Copyright 2021 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 <stdlib.h>
+#include <stdio.h>
+
+void test(int *a) {
+ // Access Go pointer out of bounds.
+ int c = a[5]; // BOOM
+ // We shouldn't get here; asan should stop us first.
+ printf("a[5]=%d\n", c);
+}
+*/
+import "C"
+
+func main() {
+ cIntSlice := []C.int{200, 201, 203, 203, 204}
+ C.test(&cIntSlice[0])
+}
--- /dev/null
+// Copyright 2021 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 <stdlib.h>
+#include <stdio.h>
+
+void test(int* a) {
+ // Access Go pointer out of bounds.
+ a[3] = 300; // BOOM
+ // We shouldn't get here; asan should stop us first.
+ printf("a[3]=%d\n", a[3]);
+}*/
+import "C"
+
+func main() {
+ var cIntArray [2]C.int
+ C.test(&cIntArray[0]) // cIntArray is moved to heap.
+}
--- /dev/null
+// Copyright 2021 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 -fsanitize=address option of C compier can detect stack-use-after-return bugs.
+// In the following program, the local variable 'local' was moved to heap by the Go
+// compiler because foo() is returning the reference to 'local', and return stack of
+// foo() will be invalid. Thus for main() to use the reference to 'local', the 'local'
+// must be available even after foo() has finished. Therefore, Go has no such issue.
+
+import "fmt"
+
+var ptr *int
+
+func main() {
+ foo()
+ fmt.Printf("ptr=%x, %v", *ptr, ptr)
+}
+
+func foo() {
+ var local int
+ local = 1
+ ptr = &local // local is moved to heap.
+}
// ASanSupported reports whether goos/goarch supports the address
// sanitizer option.
+// There is a copy of this function in misc/cgo/testsanitizers/cc_test.go.
func ASanSupported(goos, goarch string) bool {
switch goos {
case "linux":