aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/compile
diff options
context:
space:
mode:
authorfanzha02 <fannie.zhang@arm.com>2021-04-29 17:02:53 +0800
committerIan Lance Taylor <iant@google.com>2022-04-22 04:42:23 +0000
commit1e5987635cc8bf99e8a20d240da80bd6f0f793f7 (patch)
tree71b3c644dab4a84570827c63ecd01185129631ce /src/cmd/compile
parentbe1d7388b349e86bc2fc1b8769902875e732918f (diff)
downloadgo-1e5987635cc8bf99e8a20d240da80bd6f0f793f7.tar.xz
cmd/compile: enable Asan check for global variables
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 <khr@google.com> Reviewed-by: Keith Randall <khr@golang.org> Run-TryBot: Fannie Zhang <Fannie.Zhang@arm.com> Reviewed-by: Ian Lance Taylor <iant@google.com>
Diffstat (limited to 'src/cmd/compile')
-rw-r--r--src/cmd/compile/internal/base/base.go1
-rw-r--r--src/cmd/compile/internal/gc/obj.go17
-rw-r--r--src/cmd/compile/internal/noder/noder.go2
-rw-r--r--src/cmd/compile/internal/noder/object.go2
-rw-r--r--src/cmd/compile/internal/noder/reader.go2
-rw-r--r--src/cmd/compile/internal/pkginit/init.go53
-rw-r--r--src/cmd/compile/internal/pkginit/initAsanGlobals.go241
7 files changed, 313 insertions, 5 deletions
diff --git a/src/cmd/compile/internal/base/base.go b/src/cmd/compile/internal/base/base.go
index 39ce8e66f7..5e1493e275 100644
--- a/src/cmd/compile/internal/base/base.go
+++ b/src/cmd/compile/internal/base/base.go
@@ -70,6 +70,7 @@ var NoInstrumentPkgs = []string{
"runtime/msan",
"runtime/asan",
"internal/cpu",
+ "buildcfg",
}
// Don't insert racefuncenter/racefuncexit into the following packages.
diff --git a/src/cmd/compile/internal/gc/obj.go b/src/cmd/compile/internal/gc/obj.go
index fe8b6e9d45..fea2df85e5 100644
--- a/src/cmd/compile/internal/gc/obj.go
+++ b/src/cmd/compile/internal/gc/obj.go
@@ -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"
@@ -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)
@@ -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
}
diff --git a/src/cmd/compile/internal/noder/noder.go b/src/cmd/compile/internal/noder/noder.go
index 9a42b5afd1..c4c2db5f78 100644
--- a/src/cmd/compile/internal/noder/noder.go
+++ b/src/cmd/compile/internal/noder/noder.go
@@ -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
diff --git a/src/cmd/compile/internal/noder/object.go b/src/cmd/compile/internal/noder/object.go
index e8dbaac161..ee9e0e2680 100644
--- a/src/cmd/compile/internal/noder/object.go
+++ b/src/cmd/compile/internal/noder/object.go
@@ -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)
}
diff --git a/src/cmd/compile/internal/noder/reader.go b/src/cmd/compile/internal/noder/reader.go
index 1350c22467..10861717f3 100644
--- a/src/cmd/compile/internal/noder/reader.go
+++ b/src/cmd/compile/internal/noder/reader.go
@@ -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))
diff --git a/src/cmd/compile/internal/pkginit/init.go b/src/cmd/compile/internal/pkginit/init.go
index 32e95bedc2..d94482a962 100644
--- a/src/cmd/compile/internal/pkginit/init.go
+++ b/src/cmd/compile/internal/pkginit/init.go
@@ -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"
@@ -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 {
diff --git a/src/cmd/compile/internal/pkginit/initAsanGlobals.go b/src/cmd/compile/internal/pkginit/initAsanGlobals.go
new file mode 100644
index 0000000000..7276791d6e
--- /dev/null
+++ b/src/cmd/compile/internal/pkginit/initAsanGlobals.go
@@ -0,0 +1,241 @@
+// Copyright 2022 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 pkginit
+
+import (
+ "strings"
+
+ "cmd/compile/internal/base"
+ "cmd/compile/internal/ir"
+ "cmd/compile/internal/typecheck"
+ "cmd/compile/internal/types"
+ "cmd/internal/src"
+)
+
+// instrumentGlobals declares a global array of _asan_global structures and initializes it.
+func instrumentGlobals(fn *ir.Func) *ir.Name {
+ asanGlobalStruct, asanLocationStruct, defStringstruct := createtypes()
+ lname := typecheck.Lookup
+ tconv := typecheck.ConvNop
+ // Make a global array of asanGlobalStruct type.
+ // var asanglobals []asanGlobalStruct
+ arraytype := types.NewArray(asanGlobalStruct, int64(len(InstrumentGlobalsMap)))
+ symG := lname(".asanglobals")
+ globals := typecheck.NewName(symG)
+ globals.SetType(arraytype)
+ globals.Class = ir.PEXTERN
+ symG.Def = globals
+ typecheck.Target.Externs = append(typecheck.Target.Externs, globals)
+ // Make a global array of asanLocationStruct type.
+ // var asanL []asanLocationStruct
+ arraytype = types.NewArray(asanLocationStruct, int64(len(InstrumentGlobalsMap)))
+ symL := lname(".asanL")
+ asanlocation := typecheck.NewName(symL)
+ asanlocation.SetType(arraytype)
+ asanlocation.Class = ir.PEXTERN
+ symL.Def = asanlocation
+ typecheck.Target.Externs = append(typecheck.Target.Externs, asanlocation)
+ // Make three global string variables to pass the global name and module name
+ // and the name of the source file that defines it.
+ // var asanName string
+ // var asanModulename string
+ // var asanFilename string
+ symL = lname(".asanName")
+ asanName := typecheck.NewName(symL)
+ asanName.SetType(types.Types[types.TSTRING])
+ asanName.Class = ir.PEXTERN
+ symL.Def = asanName
+ typecheck.Target.Externs = append(typecheck.Target.Externs, asanName)
+
+ symL = lname(".asanModulename")
+ asanModulename := typecheck.NewName(symL)
+ asanModulename.SetType(types.Types[types.TSTRING])
+ asanModulename.Class = ir.PEXTERN
+ symL.Def = asanModulename
+ typecheck.Target.Externs = append(typecheck.Target.Externs, asanModulename)
+
+ symL = lname(".asanFilename")
+ asanFilename := typecheck.NewName(symL)
+ asanFilename.SetType(types.Types[types.TSTRING])
+ asanFilename.Class = ir.PEXTERN
+ symL.Def = asanFilename
+ typecheck.Target.Externs = append(typecheck.Target.Externs, asanFilename)
+
+ var init ir.Nodes
+ var c ir.Node
+ // globals[i].odrIndicator = 0 is the default, no need to set it explicitly here.
+ for i, n := range InstrumentGlobalsSlice {
+ setField := func(f string, val ir.Node, i int) {
+ r := ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT,
+ ir.NewIndexExpr(base.Pos, globals, ir.NewInt(int64(i))), lname(f)), val)
+ init.Append(typecheck.Stmt(r))
+ }
+ // globals[i].beg = uintptr(unsafe.Pointer(&n))
+ c = tconv(typecheck.NodAddr(n), types.Types[types.TUNSAFEPTR])
+ c = tconv(c, types.Types[types.TUINTPTR])
+ setField("beg", c, i)
+ // Assign globals[i].size.
+ g := n.(*ir.Name)
+ size := g.Type().Size()
+ c = tconv(ir.NewInt(size), types.Types[types.TUINTPTR])
+ setField("size", c, i)
+ // Assign globals[i].sizeWithRedzone.
+ rzSize := GetRedzoneSizeForGlobal(size)
+ sizeWithRz := rzSize + size
+ c = tconv(ir.NewInt(sizeWithRz), types.Types[types.TUINTPTR])
+ setField("sizeWithRedzone", c, i)
+ // The C string type is terminated by a null charactor "\0", Go should use three-digit
+ // octal "\000" or two-digit hexadecimal "\x00" to create null terminated string.
+ // asanName = symbol's linkname + "\000"
+ // globals[i].name = (*defString)(unsafe.Pointer(&asanName)).data
+ name := g.Linksym().Name
+ init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, asanName, ir.NewString(name+"\000"))))
+ c = tconv(typecheck.NodAddr(asanName), types.Types[types.TUNSAFEPTR])
+ c = tconv(c, types.NewPtr(defStringstruct))
+ c = ir.NewSelectorExpr(base.Pos, ir.ODOT, c, lname("data"))
+ setField("name", c, i)
+
+ // Set the name of package being compiled as a unique identifier of a module.
+ // asanModulename = pkgName + "\000"
+ init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, asanModulename, ir.NewString(types.LocalPkg.Name+"\000"))))
+ c = tconv(typecheck.NodAddr(asanModulename), types.Types[types.TUNSAFEPTR])
+ c = tconv(c, types.NewPtr(defStringstruct))
+ c = ir.NewSelectorExpr(base.Pos, ir.ODOT, c, lname("data"))
+ setField("moduleName", c, i)
+ // Assign asanL[i].filename, asanL[i].line, asanL[i].column
+ // and assign globals[i].location = uintptr(unsafe.Pointer(&asanL[i]))
+ asanLi := ir.NewIndexExpr(base.Pos, asanlocation, ir.NewInt(int64(i)))
+ filename := ir.NewString(base.Ctxt.PosTable.Pos(n.Pos()).Filename() + "\000")
+ init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, asanFilename, filename)))
+ c = tconv(typecheck.NodAddr(asanFilename), types.Types[types.TUNSAFEPTR])
+ c = tconv(c, types.NewPtr(defStringstruct))
+ c = ir.NewSelectorExpr(base.Pos, ir.ODOT, c, lname("data"))
+ init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, asanLi, lname("filename")), c)))
+ line := ir.NewInt(int64(n.Pos().Line()))
+ init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, asanLi, lname("line")), line)))
+ col := ir.NewInt(int64(n.Pos().Col()))
+ init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, asanLi, lname("column")), col)))
+ c = tconv(typecheck.NodAddr(asanLi), types.Types[types.TUNSAFEPTR])
+ c = tconv(c, types.Types[types.TUINTPTR])
+ setField("sourceLocation", c, i)
+ }
+ fn.Body.Append(init...)
+ return globals
+}
+
+// createtypes creates the asanGlobal, asanLocation and defString struct type.
+// Go compiler does not refer to the C types, we represent the struct field
+// by a uintptr, then use type conversion to make copies of the data.
+// E.g., (*defString)(asanGlobal.name).data to C string.
+//
+// Keep in sync with src/runtime/asan/asan.go.
+// type asanGlobal struct {
+// beg uintptr
+// size uintptr
+// size_with_redzone uintptr
+// name uintptr
+// moduleName uintptr
+// hasDynamicInit uintptr
+// sourceLocation uintptr
+// odrIndicator uintptr
+// }
+//
+// type asanLocation struct {
+// filename uintptr
+// line int32
+// column int32
+// }
+//
+// defString is synthesized struct type meant to capture the underlying
+// implementations of string.
+// type defString struct {
+// data uintptr
+// len uintptr
+// }
+
+func createtypes() (*types.Type, *types.Type, *types.Type) {
+ up := types.Types[types.TUINTPTR]
+ i32 := types.Types[types.TINT32]
+ fname := typecheck.Lookup
+ nxp := src.NoXPos
+ nfield := types.NewField
+ asanGlobal := types.NewStruct(types.NoPkg, []*types.Field{
+ nfield(nxp, fname("beg"), up),
+ nfield(nxp, fname("size"), up),
+ nfield(nxp, fname("sizeWithRedzone"), up),
+ nfield(nxp, fname("name"), up),
+ nfield(nxp, fname("moduleName"), up),
+ nfield(nxp, fname("hasDynamicInit"), up),
+ nfield(nxp, fname("sourceLocation"), up),
+ nfield(nxp, fname("odrIndicator"), up),
+ })
+ types.CalcSize(asanGlobal)
+
+ asanLocation := types.NewStruct(types.NoPkg, []*types.Field{
+ nfield(nxp, fname("filename"), up),
+ nfield(nxp, fname("line"), i32),
+ nfield(nxp, fname("column"), i32),
+ })
+ types.CalcSize(asanLocation)
+
+ defString := types.NewStruct(types.NoPkg, []*types.Field{
+ types.NewField(nxp, fname("data"), up),
+ types.NewField(nxp, fname("len"), up),
+ })
+ types.CalcSize(defString)
+
+ return asanGlobal, asanLocation, defString
+}
+
+// Calculate redzone for globals.
+func GetRedzoneSizeForGlobal(size int64) int64 {
+ maxRZ := int64(1 << 18)
+ minRZ := int64(32)
+ redZone := (size / minRZ / 4) * minRZ
+ switch {
+ case redZone > maxRZ:
+ redZone = maxRZ
+ case redZone < minRZ:
+ redZone = minRZ
+ }
+ // Round up to multiple of minRZ.
+ if size%minRZ != 0 {
+ redZone += minRZ - (size % minRZ)
+ }
+ return redZone
+}
+
+// InstrumentGlobalsMap contains only package-local (and unlinknamed from somewhere else)
+// globals.
+// And the key is the object name. For example, in package p, a global foo would be in this
+// map as "foo".
+// Consider range over maps is nondeterministic, make a slice to hold all the values in the
+// InstrumentGlobalsMap and iterate over the InstrumentGlobalsSlice.
+var InstrumentGlobalsMap = make(map[string]ir.Node)
+var InstrumentGlobalsSlice = make([]ir.Node, 0, 0)
+
+func canInstrumentGlobal(g ir.Node) bool {
+ if g.Op() != ir.ONAME {
+ return false
+ }
+ n := g.(*ir.Name)
+ if n.Class == ir.PFUNC {
+ return false
+ }
+ if n.Sym().Pkg != types.LocalPkg {
+ return false
+ }
+ // Do not instrument any _cgo_ related global variables, because they are declared in C code.
+ if strings.Contains(n.Sym().Name, "cgo") {
+ return false
+ }
+
+ // Do not instrument globals that are linknamed, because their home package will do the work.
+ if n.Sym().Linkname != "" {
+ return false
+ }
+
+ return true
+}