mirror of
https://github.com/golang/go
synced 2024-11-23 07:40:04 -07:00
cmd/compile: add -d=checkptr to validate unsafe.Pointer rules
This CL adds -d=checkptr as a compile-time option for adding instrumentation to check that Go code is following unsafe.Pointer safety rules dynamically. In particular, it currently checks two things: 1. When converting unsafe.Pointer to *T, make sure the resulting pointer is aligned appropriately for T. 2. When performing pointer arithmetic, if the result points to a Go heap object, make sure we can find an unsafe.Pointer-typed operand that pointed into the same object. These checks are currently disabled for the runtime, and can also be disabled through a new //go:nocheckptr annotation. The latter is necessary for functions like strings.noescape, which intentionally violate safety rules to workaround escape analysis limitations. Fixes #22218. Change-Id: If5a51273881d93048f74bcff10a3275c9c91da6a Reviewed-on: https://go-review.googlesource.com/c/go/+/162237 Run-TryBot: Matthew Dempsky <mdempsky@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
parent
3b003c3edb
commit
80a6fedea0
@ -181,13 +181,15 @@ var runtimeDecls = [...]struct {
|
||||
{"racewriterange", funcTag, 119},
|
||||
{"msanread", funcTag, 119},
|
||||
{"msanwrite", funcTag, 119},
|
||||
{"checkptrAlignment", funcTag, 120},
|
||||
{"checkptrArithmetic", funcTag, 122},
|
||||
{"x86HasPOPCNT", varTag, 15},
|
||||
{"x86HasSSE41", varTag, 15},
|
||||
{"arm64HasATOMICS", varTag, 15},
|
||||
}
|
||||
|
||||
func runtimeTypes() []*types.Type {
|
||||
var typs [120]*types.Type
|
||||
var typs [123]*types.Type
|
||||
typs[0] = types.Bytetype
|
||||
typs[1] = types.NewPtr(typs[0])
|
||||
typs[2] = types.Types[TANY]
|
||||
@ -308,5 +310,8 @@ func runtimeTypes() []*types.Type {
|
||||
typs[117] = functype(nil, []*Node{anonfield(typs[23]), anonfield(typs[23])}, []*Node{anonfield(typs[23])})
|
||||
typs[118] = functype(nil, []*Node{anonfield(typs[50])}, nil)
|
||||
typs[119] = functype(nil, []*Node{anonfield(typs[50]), anonfield(typs[50])}, nil)
|
||||
typs[120] = functype(nil, []*Node{anonfield(typs[56]), anonfield(typs[1])}, nil)
|
||||
typs[121] = types.NewSlice(typs[56])
|
||||
typs[122] = functype(nil, []*Node{anonfield(typs[56]), anonfield(typs[121])}, nil)
|
||||
return typs[:]
|
||||
}
|
||||
|
@ -235,6 +235,9 @@ func racewriterange(addr, size uintptr)
|
||||
func msanread(addr, size uintptr)
|
||||
func msanwrite(addr, size uintptr)
|
||||
|
||||
func checkptrAlignment(unsafe.Pointer, *byte)
|
||||
func checkptrArithmetic(unsafe.Pointer, []unsafe.Pointer)
|
||||
|
||||
// architecture variants
|
||||
var x86HasPOPCNT bool
|
||||
var x86HasSSE41 bool
|
||||
|
@ -135,6 +135,12 @@ func caninl(fn *Node) {
|
||||
return
|
||||
}
|
||||
|
||||
// If marked "go:nocheckptr" and -d checkptr compilation, don't inline.
|
||||
if Debug_checkptr != 0 && fn.Func.Pragma&NoCheckPtr != 0 {
|
||||
reason = "marked go:nocheckptr"
|
||||
return
|
||||
}
|
||||
|
||||
// If marked "go:cgo_unsafe_args", don't inline, since the
|
||||
// function makes assumptions about its argument frame layout.
|
||||
if fn.Func.Pragma&CgoUnsafeArgs != 0 {
|
||||
|
@ -35,6 +35,7 @@ const (
|
||||
Norace // func must not have race detector annotations
|
||||
Nosplit // func should not execute on separate stack
|
||||
Noinline // func should not be inlined
|
||||
NoCheckPtr // func should not be instrumented by checkptr
|
||||
CgoUnsafeArgs // treat a pointer to one arg as a pointer to them all
|
||||
UintptrEscapes // pointers converted to uintptr escape
|
||||
|
||||
@ -63,6 +64,8 @@ func pragmaValue(verb string) syntax.Pragma {
|
||||
return Nosplit
|
||||
case "go:noinline":
|
||||
return Noinline
|
||||
case "go:nocheckptr":
|
||||
return NoCheckPtr
|
||||
case "go:systemstack":
|
||||
return Systemstack
|
||||
case "go:nowritebarrier":
|
||||
|
@ -40,6 +40,7 @@ var (
|
||||
|
||||
var (
|
||||
Debug_append int
|
||||
Debug_checkptr int
|
||||
Debug_closure int
|
||||
Debug_compilelater int
|
||||
debug_dclstack int
|
||||
@ -65,6 +66,7 @@ var debugtab = []struct {
|
||||
val interface{} // must be *int or *string
|
||||
}{
|
||||
{"append", "print information about append compilation", &Debug_append},
|
||||
{"checkptr", "instrument unsafe pointer conversions", &Debug_checkptr},
|
||||
{"closure", "print information about closure compilation", &Debug_closure},
|
||||
{"compilelater", "compile functions as late as possible", &Debug_compilelater},
|
||||
{"disablenil", "disable nil checks", &disable_checknil},
|
||||
@ -433,6 +435,11 @@ func Main(archInit func(*Arch)) {
|
||||
}
|
||||
}
|
||||
|
||||
// Runtime can't use -d=checkptr, at least not yet.
|
||||
if compiling_runtime {
|
||||
Debug_checkptr = 0
|
||||
}
|
||||
|
||||
// set via a -d flag
|
||||
Ctxt.Debugpcln = Debug_pctab
|
||||
if flagDWARF {
|
||||
|
@ -951,6 +951,16 @@ opswitch:
|
||||
|
||||
case OCONV, OCONVNOP:
|
||||
n.Left = walkexpr(n.Left, init)
|
||||
if n.Op == OCONVNOP && Debug_checkptr != 0 && Curfn.Func.Pragma&NoCheckPtr == 0 {
|
||||
if n.Type.IsPtr() && n.Left.Type.Etype == TUNSAFEPTR { // unsafe.Pointer to *T
|
||||
n = walkCheckPtrAlignment(n, init)
|
||||
break
|
||||
}
|
||||
if n.Type.Etype == TUNSAFEPTR && n.Left.Type.Etype == TUINTPTR { // uintptr to unsafe.Pointer
|
||||
n = walkCheckPtrArithmetic(n, init)
|
||||
break
|
||||
}
|
||||
}
|
||||
param, result := rtconvfn(n.Left.Type, n.Type)
|
||||
if param == Txxx {
|
||||
break
|
||||
@ -3898,3 +3908,66 @@ func canMergeLoads() bool {
|
||||
func isRuneCount(n *Node) bool {
|
||||
return Debug['N'] == 0 && !instrumenting && n.Op == OLEN && n.Left.Op == OSTR2RUNES
|
||||
}
|
||||
|
||||
func walkCheckPtrAlignment(n *Node, init *Nodes) *Node {
|
||||
if n.Type.Elem().Alignment() == 1 {
|
||||
return n
|
||||
}
|
||||
|
||||
n.Left = cheapexpr(n.Left, init)
|
||||
init.Append(mkcall("checkptrAlignment", nil, init, n.Left, typename(n.Type.Elem())))
|
||||
return n
|
||||
}
|
||||
|
||||
var walkCheckPtrArithmeticMarker byte
|
||||
|
||||
func walkCheckPtrArithmetic(n *Node, init *Nodes) *Node {
|
||||
// Calling cheapexpr(n, init) below leads to a recursive call
|
||||
// to walkexpr, which leads us back here again. Use n.Opt to
|
||||
// prevent infinite loops.
|
||||
if n.Opt() == &walkCheckPtrArithmeticMarker {
|
||||
return n
|
||||
}
|
||||
n.SetOpt(&walkCheckPtrArithmeticMarker)
|
||||
defer n.SetOpt(nil)
|
||||
|
||||
// TODO(mdempsky): Make stricter. We only need to exempt
|
||||
// reflect.Value.Pointer and reflect.Value.UnsafeAddr.
|
||||
switch n.Left.Op {
|
||||
case OCALLFUNC, OCALLMETH, OCALLINTER:
|
||||
return n
|
||||
}
|
||||
|
||||
// Find original unsafe.Pointer operands involved in this
|
||||
// arithmetic expression.
|
||||
//
|
||||
// "It is valid both to add and to subtract offsets from a
|
||||
// pointer in this way. It is also valid to use &^ to round
|
||||
// pointers, usually for alignment."
|
||||
var originals []*Node
|
||||
var walk func(n *Node)
|
||||
walk = func(n *Node) {
|
||||
switch n.Op {
|
||||
case OADD:
|
||||
walk(n.Left)
|
||||
walk(n.Right)
|
||||
case OSUB, OANDNOT:
|
||||
walk(n.Left)
|
||||
case OCONVNOP:
|
||||
if n.Left.Type.Etype == TUNSAFEPTR {
|
||||
n.Left = cheapexpr(n.Left, init)
|
||||
originals = append(originals, n.Left)
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(n.Left)
|
||||
|
||||
n = cheapexpr(n, init)
|
||||
|
||||
slice := mkdotargslice(types.NewSlice(types.Types[TUNSAFEPTR]), originals, init, nil)
|
||||
slice.Esc = EscNone
|
||||
slice.SetTransient(true)
|
||||
|
||||
init.Append(mkcall("checkptrArithmetic", nil, init, n, slice))
|
||||
return n
|
||||
}
|
||||
|
@ -1407,6 +1407,11 @@ func (v Value) OverflowUint(x uint64) bool {
|
||||
panic(&ValueError{"reflect.Value.OverflowUint", v.kind()})
|
||||
}
|
||||
|
||||
//go:nocheckptr
|
||||
// This prevents inlining Value.Pointer when -d=checkptr is enabled,
|
||||
// which ensures cmd/compile can recognize unsafe.Pointer(v.Pointer())
|
||||
// and make an exception.
|
||||
|
||||
// Pointer returns v's value as a uintptr.
|
||||
// It returns uintptr instead of unsafe.Pointer so that
|
||||
// code using reflect cannot obtain unsafe.Pointers
|
||||
@ -1914,6 +1919,11 @@ func (v Value) Uint() uint64 {
|
||||
panic(&ValueError{"reflect.Value.Uint", v.kind()})
|
||||
}
|
||||
|
||||
//go:nocheckptr
|
||||
// This prevents inlining Value.UnsafeAddr when -d=checkptr is enabled,
|
||||
// which ensures cmd/compile can recognize unsafe.Pointer(v.UnsafeAddr())
|
||||
// and make an exception.
|
||||
|
||||
// UnsafeAddr returns a pointer to v's data.
|
||||
// It is for advanced clients that also import the "unsafe" package.
|
||||
// It panics if v is not addressable.
|
||||
|
50
src/runtime/checkptr.go
Normal file
50
src/runtime/checkptr.go
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright 2019 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 runtime
|
||||
|
||||
import "unsafe"
|
||||
|
||||
type ptrAlign struct {
|
||||
ptr unsafe.Pointer
|
||||
align uintptr
|
||||
}
|
||||
|
||||
func checkptrAlignment(p unsafe.Pointer, elem *_type) {
|
||||
// TODO(mdempsky): What about fieldAlign?
|
||||
if uintptr(p)&(uintptr(elem.align)-1) != 0 {
|
||||
panic(ptrAlign{p, uintptr(elem.align)})
|
||||
}
|
||||
}
|
||||
|
||||
type ptrArith struct {
|
||||
ptr unsafe.Pointer
|
||||
originals []unsafe.Pointer
|
||||
}
|
||||
|
||||
func checkptrArithmetic(p unsafe.Pointer, originals []unsafe.Pointer) {
|
||||
if 0 < uintptr(p) && uintptr(p) < minLegalPointer {
|
||||
panic(ptrArith{p, originals})
|
||||
}
|
||||
|
||||
base := checkptrBase(p)
|
||||
if base == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, original := range originals {
|
||||
if base == checkptrBase(original) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
panic(ptrArith{p, originals})
|
||||
}
|
||||
|
||||
func checkptrBase(p unsafe.Pointer) uintptr {
|
||||
base, _, _ := findObject(uintptr(p), 0, 0)
|
||||
// TODO(mdempsky): If base == 0, then check if p points to the
|
||||
// stack or a global variable.
|
||||
return base
|
||||
}
|
@ -24,6 +24,7 @@ type Builder struct {
|
||||
// USE CAREFULLY!
|
||||
// This was copied from the runtime; see issues 23382 and 7921.
|
||||
//go:nosplit
|
||||
//go:nocheckptr
|
||||
func noescape(p unsafe.Pointer) unsafe.Pointer {
|
||||
x := uintptr(p)
|
||||
return unsafe.Pointer(x ^ 0)
|
||||
|
Loading…
Reference in New Issue
Block a user