Skip to content

Commit

Permalink
cmd/compile: enable Asan check for global variables
Browse files Browse the repository at this point in the history
With this patch, -asan option can detect the error memory
access to global variables.

So this patch makes a few changes:

1. Add the asanregisterglobals runtime support function,
which calls asan runtime function _asan_register_globals
to register global variables.

2. Create a new initialization function for the package
being compiled. This function initializes an array of
instrumented global variables and pass it to function
runtime.asanregisterglobals. An instrumented global
variable has trailing redzone.

3. Writes the new size of instrumented global variables
that have trailing redzones into object file.

4. Notice that the current implementation is only compatible with
the ASan library from version v7 to v9. Therefore, using the
-asan option requires that the gcc version is not less than 7
and the clang version is less than 4, otherwise a segmentation
fault will occur. So this patch adds a check on whether the compiler
being used is a supported version in cmd/go.

Change-Id: I664e74dcabf5dc7ed46802859174606454e8f1d3
Reviewed-on: https://go-review.googlesource.com/c/go/+/321715
Reviewed-by: Keith Randall <[email protected]>
Reviewed-by: Keith Randall <[email protected]>
Run-TryBot: Fannie Zhang <[email protected]>
Reviewed-by: Ian Lance Taylor <[email protected]>
  • Loading branch information
zhangfannie authored and Ian Lance Taylor committed Apr 22, 2022
1 parent be1d738 commit 1e59876
Show file tree
Hide file tree
Showing 17 changed files with 461 additions and 9 deletions.
8 changes: 8 additions & 0 deletions misc/cgo/testsanitizers/asan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ func TestASAN(t *testing.T) {
if !aSanSupported(goos, goarch) {
t.Skipf("skipping on %s/%s; -asan option is not supported.", goos, goarch)
}
// The current implementation is only compatible with the ASan library from version
// v7 to v9 (See the description in src/runtime/asan/asan.go). Therefore, using the
// -asan option must use a compatible version of ASan library, which requires that
// the gcc version is not less than 7 and the clang version is not less than 4,
// otherwise a segmentation fault will occur.
if !compilerRequiredAsanVersion() {
t.Skipf("skipping: too old version of compiler")
}

t.Parallel()
requireOvercommit(t)
Expand Down
16 changes: 16 additions & 0 deletions misc/cgo/testsanitizers/cc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,22 @@ func compilerSupportsLocation() bool {
}
}

// compilerRequiredAsanVersion reports whether the compiler is the version required by Asan.
func compilerRequiredAsanVersion() bool {
compiler, err := compilerVersion()
if err != nil {
return false
}
switch compiler.name {
case "gcc":
return compiler.major >= 7
case "clang":
return true
default:
return false
}
}

type compilerCheck struct {
once sync.Once
err error
Expand Down
1 change: 1 addition & 0 deletions src/cmd/compile/internal/base/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ var NoInstrumentPkgs = []string{
"runtime/msan",
"runtime/asan",
"internal/cpu",
"buildcfg",
}

// Don't insert racefuncenter/racefuncexit into the following packages.
Expand Down
17 changes: 15 additions & 2 deletions src/cmd/compile/internal/gc/obj.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"cmd/compile/internal/ir"
"cmd/compile/internal/noder"
"cmd/compile/internal/objw"
"cmd/compile/internal/pkginit"
"cmd/compile/internal/reflectdata"
"cmd/compile/internal/staticdata"
"cmd/compile/internal/typecheck"
Expand Down Expand Up @@ -110,7 +111,6 @@ func dumpCompilerObj(bout *bio.Writer) {
func dumpdata() {
numExterns := len(typecheck.Target.Externs)
numDecls := len(typecheck.Target.Decls)

dumpglobls(typecheck.Target.Externs)
reflectdata.CollectPTabs()
numExports := len(typecheck.Target.Exports)
Expand Down Expand Up @@ -287,7 +287,20 @@ func ggloblnod(nam *ir.Name) {
if nam.Type() != nil && !nam.Type().HasPointers() {
flags |= obj.NOPTR
}
base.Ctxt.Globl(s, nam.Type().Size(), flags)
size := nam.Type().Size()
linkname := nam.Sym().Linkname
name := nam.Sym().Name

// We've skipped linkname'd globals's instrument, so we can skip them here as well.
if base.Flag.ASan && linkname == "" && pkginit.InstrumentGlobalsMap[name] != nil {
// Write the new size of instrumented global variables that have
// trailing redzones into object file.
rzSize := pkginit.GetRedzoneSizeForGlobal(size)
sizeWithRZ := rzSize + size
base.Ctxt.Globl(s, sizeWithRZ, flags)
} else {
base.Ctxt.Globl(s, size, flags)
}
if nam.LibfuzzerExtraCounter() {
s.Type = objabi.SLIBFUZZER_EXTRA_COUNTER
}
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/noder/noder.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ func parseGoEmbed(args string) ([]string, error) {
// the name, normally "pkg.init", is altered to "pkg.init.0".
var renameinitgen int

func renameinit() *types.Sym {
func Renameinit() *types.Sym {
s := typecheck.LookupNum("init.", renameinitgen)
renameinitgen++
return s
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/noder/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func (g *irgen) obj(obj types2.Object) *ir.Name {
var typ *types.Type
if recv := sig.Recv(); recv == nil {
if obj.Name() == "init" {
sym = renameinit()
sym = Renameinit()
} else {
sym = g.sym(obj)
}
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/noder/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ func (pr *pkgReader) objIdx(idx int, implicits, explicits []*types.Type) ir.Node

case pkgbits.ObjFunc:
if sym.Name == "init" {
sym = renameinit()
sym = Renameinit()
}
name := do(ir.ONAME, true)
setType(name, r.signature(sym.Pkg, nil))
Expand Down
53 changes: 53 additions & 0 deletions src/cmd/compile/internal/pkginit/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package pkginit
import (
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/noder"
"cmd/compile/internal/objw"
"cmd/compile/internal/staticinit"
"cmd/compile/internal/typecheck"
Expand Down Expand Up @@ -83,6 +84,58 @@ func Task() *ir.Name {
}
deps = append(deps, n.(*ir.Name).Linksym())
}
if base.Flag.ASan {
// Make an initialization function to call runtime.asanregisterglobals to register an
// array of instrumented global variables when -asan is enabled. An instrumented global
// variable is described by a structure.
// See the _asan_global structure declared in src/runtime/asan/asan.go.
//
// func init {
// var globals []_asan_global {...}
// asanregisterglobals(&globals[0], len(globals))
// }
for _, n := range typecheck.Target.Externs {
if canInstrumentGlobal(n) {
name := n.Sym().Name
InstrumentGlobalsMap[name] = n
InstrumentGlobalsSlice = append(InstrumentGlobalsSlice, n)
}
}
ni := len(InstrumentGlobalsMap)
if ni != 0 {
// Make an init._ function.
base.Pos = base.AutogeneratedPos
typecheck.DeclContext = ir.PEXTERN
name := noder.Renameinit()
fnInit := typecheck.DeclFunc(name, ir.NewFuncType(base.Pos, nil, nil, nil))

// Get an array of intrumented global variables.
globals := instrumentGlobals(fnInit)

// Call runtime.asanregisterglobals function to poison redzones.
// runtime.asanregisterglobals(unsafe.Pointer(&globals[0]), ni)
asanf := typecheck.NewName(ir.Pkgs.Runtime.Lookup("asanregisterglobals"))
ir.MarkFunc(asanf)
asanf.SetType(types.NewSignature(types.NoPkg, nil, nil, []*types.Field{
types.NewField(base.Pos, nil, types.Types[types.TUNSAFEPTR]),
types.NewField(base.Pos, nil, types.Types[types.TUINTPTR]),
}, nil))
asancall := ir.NewCallExpr(base.Pos, ir.OCALL, asanf, nil)
asancall.Args.Append(typecheck.ConvNop(typecheck.NodAddr(
ir.NewIndexExpr(base.Pos, globals, ir.NewInt(0))), types.Types[types.TUNSAFEPTR]))
asancall.Args.Append(typecheck.ConvNop(ir.NewInt(int64(ni)), types.Types[types.TUINTPTR]))

fnInit.Body.Append(asancall)
typecheck.FinishFuncBody()
typecheck.Func(fnInit)
ir.CurFunc = fnInit
typecheck.Stmts(fnInit.Body)
ir.CurFunc = nil

typecheck.Target.Decls = append(typecheck.Target.Decls, fnInit)
typecheck.Target.Inits = append(typecheck.Target.Inits, fnInit)
}
}

// Record user init functions.
for _, fn := range typecheck.Target.Inits {
Expand Down
Loading

0 comments on commit 1e59876

Please sign in to comment.