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.

(This is a redo of CL 401775 with a fix for a build break due to an
intervening commit that removed the internal/execabs package.)

Updates golang#44853.

Change-Id: I719d4ef2b22cb2d5516e1494cd453c3efb47d6c7
Reviewed-on: https://go-review.googlesource.com/c/go/+/403851
Auto-Submit: Bryan Mills <[email protected]>
Run-TryBot: Bryan Mills <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
Reviewed-by: Ian Lance Taylor <[email protected]>
  • Loading branch information
zhangfannie authored and gopherbot committed May 4, 2022
1 parent c570f0e commit 1b0f9fb
Show file tree
Hide file tree
Showing 18 changed files with 509 additions and 15 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 9,
// otherwise a segmentation fault will occur.
if !compilerRequiredAsanVersion() {
t.Skipf("skipping: too old version of compiler")
}

t.Parallel()
requireOvercommit(t)
Expand Down
25 changes: 20 additions & 5 deletions misc/cgo/testsanitizers/cc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,17 +184,16 @@ func compilerVersion() (version, error) {
var match [][]byte
if bytes.HasPrefix(out, []byte("gcc")) {
compiler.name = "gcc"

cmd, err := cc("-dumpversion")
cmd, err := cc("-v")
if err != nil {
return err
}
out, err := cmd.Output()
out, err := cmd.CombinedOutput()
if err != nil {
// gcc, but does not support gcc's "-dumpversion" flag?!
// gcc, but does not support gcc's "-v" flag?!
return err
}
gccRE := regexp.MustCompile(`(\d+)\.(\d+)`)
gccRE := regexp.MustCompile(`gcc version (\d+)\.(\d+)`)
match = gccRE.FindSubmatch(out)
} else {
clangRE := regexp.MustCompile(`clang version (\d+)\.(\d+)`)
Expand Down Expand Up @@ -235,6 +234,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 compiler.major >= 9
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 @@ -645,7 +645,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 1b0f9fb

Please sign in to comment.