Skip to content

Commit

Permalink
cmd/dist: add asan tests in misc/cgo/testsanitizers package
Browse files Browse the repository at this point in the history
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 <[email protected]>
Run-TryBot: fannie zhang <[email protected]>
Run-TryBot: Ian Lance Taylor <[email protected]>
TryBot-Result: Go Bot <[email protected]>
Reviewed-by: Ian Lance Taylor <[email protected]>
  • Loading branch information
zhangfannie authored and ianlancetaylor committed Nov 2, 2021
1 parent c1ea339 commit 3ee426a
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 0 deletions.
66 changes: 66 additions & 0 deletions misc/cgo/testsanitizers/asan_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// 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)
})
}
}
14 changes: 14 additions & 0 deletions misc/cgo/testsanitizers/cc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ func configure(sanitizer string) *config {
c.ldFlags = append(c.ldFlags, "-fPIC", "-static-libtsan")
}

case "address":
c.goFlags = append(c.goFlags, "-asan")

default:
panic(fmt.Sprintf("unrecognized sanitizer: %q", sanitizer))
}
Expand Down Expand Up @@ -450,3 +453,14 @@ func mSanSupported(goos, goarch string) bool {
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
}
}
28 changes: 28 additions & 0 deletions misc/cgo/testsanitizers/testdata/asan1_fail.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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)
}
34 changes: 34 additions & 0 deletions misc/cgo/testsanitizers/testdata/asan2_fail.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 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)
}
23 changes: 23 additions & 0 deletions misc/cgo/testsanitizers/testdata/asan3_fail.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// 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])
}
22 changes: 22 additions & 0 deletions misc/cgo/testsanitizers/testdata/asan4_fail.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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.
}
26 changes: 26 additions & 0 deletions misc/cgo/testsanitizers/testdata/asan_useAfterReturn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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.
}
1 change: 1 addition & 0 deletions src/cmd/internal/sys/supported.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func MSanSupported(goos, goarch string) bool {

// 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":
Expand Down

0 comments on commit 3ee426a

Please sign in to comment.