Skip to content

Commit 80a6fed

Browse files
committed
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 <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent 3b003c3 commit 80a6fed

File tree

9 files changed

+159
-1
lines changed

9 files changed

+159
-1
lines changed

src/cmd/compile/internal/gc/builtin.go

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/compile/internal/gc/builtin/runtime.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,9 @@ func racewriterange(addr, size uintptr)
235235
func msanread(addr, size uintptr)
236236
func msanwrite(addr, size uintptr)
237237

238+
func checkptrAlignment(unsafe.Pointer, *byte)
239+
func checkptrArithmetic(unsafe.Pointer, []unsafe.Pointer)
240+
238241
// architecture variants
239242
var x86HasPOPCNT bool
240243
var x86HasSSE41 bool

src/cmd/compile/internal/gc/inl.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,12 @@ func caninl(fn *Node) {
135135
return
136136
}
137137

138+
// If marked "go:nocheckptr" and -d checkptr compilation, don't inline.
139+
if Debug_checkptr != 0 && fn.Func.Pragma&NoCheckPtr != 0 {
140+
reason = "marked go:nocheckptr"
141+
return
142+
}
143+
138144
// If marked "go:cgo_unsafe_args", don't inline, since the
139145
// function makes assumptions about its argument frame layout.
140146
if fn.Func.Pragma&CgoUnsafeArgs != 0 {

src/cmd/compile/internal/gc/lex.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const (
3535
Norace // func must not have race detector annotations
3636
Nosplit // func should not execute on separate stack
3737
Noinline // func should not be inlined
38+
NoCheckPtr // func should not be instrumented by checkptr
3839
CgoUnsafeArgs // treat a pointer to one arg as a pointer to them all
3940
UintptrEscapes // pointers converted to uintptr escape
4041

@@ -63,6 +64,8 @@ func pragmaValue(verb string) syntax.Pragma {
6364
return Nosplit
6465
case "go:noinline":
6566
return Noinline
67+
case "go:nocheckptr":
68+
return NoCheckPtr
6669
case "go:systemstack":
6770
return Systemstack
6871
case "go:nowritebarrier":

src/cmd/compile/internal/gc/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ var (
4040

4141
var (
4242
Debug_append int
43+
Debug_checkptr int
4344
Debug_closure int
4445
Debug_compilelater int
4546
debug_dclstack int
@@ -65,6 +66,7 @@ var debugtab = []struct {
6566
val interface{} // must be *int or *string
6667
}{
6768
{"append", "print information about append compilation", &Debug_append},
69+
{"checkptr", "instrument unsafe pointer conversions", &Debug_checkptr},
6870
{"closure", "print information about closure compilation", &Debug_closure},
6971
{"compilelater", "compile functions as late as possible", &Debug_compilelater},
7072
{"disablenil", "disable nil checks", &disable_checknil},
@@ -433,6 +435,11 @@ func Main(archInit func(*Arch)) {
433435
}
434436
}
435437

438+
// Runtime can't use -d=checkptr, at least not yet.
439+
if compiling_runtime {
440+
Debug_checkptr = 0
441+
}
442+
436443
// set via a -d flag
437444
Ctxt.Debugpcln = Debug_pctab
438445
if flagDWARF {

src/cmd/compile/internal/gc/walk.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,16 @@ opswitch:
951951

952952
case OCONV, OCONVNOP:
953953
n.Left = walkexpr(n.Left, init)
954+
if n.Op == OCONVNOP && Debug_checkptr != 0 && Curfn.Func.Pragma&NoCheckPtr == 0 {
955+
if n.Type.IsPtr() && n.Left.Type.Etype == TUNSAFEPTR { // unsafe.Pointer to *T
956+
n = walkCheckPtrAlignment(n, init)
957+
break
958+
}
959+
if n.Type.Etype == TUNSAFEPTR && n.Left.Type.Etype == TUINTPTR { // uintptr to unsafe.Pointer
960+
n = walkCheckPtrArithmetic(n, init)
961+
break
962+
}
963+
}
954964
param, result := rtconvfn(n.Left.Type, n.Type)
955965
if param == Txxx {
956966
break
@@ -3898,3 +3908,66 @@ func canMergeLoads() bool {
38983908
func isRuneCount(n *Node) bool {
38993909
return Debug['N'] == 0 && !instrumenting && n.Op == OLEN && n.Left.Op == OSTR2RUNES
39003910
}
3911+
3912+
func walkCheckPtrAlignment(n *Node, init *Nodes) *Node {
3913+
if n.Type.Elem().Alignment() == 1 {
3914+
return n
3915+
}
3916+
3917+
n.Left = cheapexpr(n.Left, init)
3918+
init.Append(mkcall("checkptrAlignment", nil, init, n.Left, typename(n.Type.Elem())))
3919+
return n
3920+
}
3921+
3922+
var walkCheckPtrArithmeticMarker byte
3923+
3924+
func walkCheckPtrArithmetic(n *Node, init *Nodes) *Node {
3925+
// Calling cheapexpr(n, init) below leads to a recursive call
3926+
// to walkexpr, which leads us back here again. Use n.Opt to
3927+
// prevent infinite loops.
3928+
if n.Opt() == &walkCheckPtrArithmeticMarker {
3929+
return n
3930+
}
3931+
n.SetOpt(&walkCheckPtrArithmeticMarker)
3932+
defer n.SetOpt(nil)
3933+
3934+
// TODO(mdempsky): Make stricter. We only need to exempt
3935+
// reflect.Value.Pointer and reflect.Value.UnsafeAddr.
3936+
switch n.Left.Op {
3937+
case OCALLFUNC, OCALLMETH, OCALLINTER:
3938+
return n
3939+
}
3940+
3941+
// Find original unsafe.Pointer operands involved in this
3942+
// arithmetic expression.
3943+
//
3944+
// "It is valid both to add and to subtract offsets from a
3945+
// pointer in this way. It is also valid to use &^ to round
3946+
// pointers, usually for alignment."
3947+
var originals []*Node
3948+
var walk func(n *Node)
3949+
walk = func(n *Node) {
3950+
switch n.Op {
3951+
case OADD:
3952+
walk(n.Left)
3953+
walk(n.Right)
3954+
case OSUB, OANDNOT:
3955+
walk(n.Left)
3956+
case OCONVNOP:
3957+
if n.Left.Type.Etype == TUNSAFEPTR {
3958+
n.Left = cheapexpr(n.Left, init)
3959+
originals = append(originals, n.Left)
3960+
}
3961+
}
3962+
}
3963+
walk(n.Left)
3964+
3965+
n = cheapexpr(n, init)
3966+
3967+
slice := mkdotargslice(types.NewSlice(types.Types[TUNSAFEPTR]), originals, init, nil)
3968+
slice.Esc = EscNone
3969+
slice.SetTransient(true)
3970+
3971+
init.Append(mkcall("checkptrArithmetic", nil, init, n, slice))
3972+
return n
3973+
}

src/reflect/value.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,6 +1407,11 @@ func (v Value) OverflowUint(x uint64) bool {
14071407
panic(&ValueError{"reflect.Value.OverflowUint", v.kind()})
14081408
}
14091409

1410+
//go:nocheckptr
1411+
// This prevents inlining Value.Pointer when -d=checkptr is enabled,
1412+
// which ensures cmd/compile can recognize unsafe.Pointer(v.Pointer())
1413+
// and make an exception.
1414+
14101415
// Pointer returns v's value as a uintptr.
14111416
// It returns uintptr instead of unsafe.Pointer so that
14121417
// code using reflect cannot obtain unsafe.Pointers
@@ -1914,6 +1919,11 @@ func (v Value) Uint() uint64 {
19141919
panic(&ValueError{"reflect.Value.Uint", v.kind()})
19151920
}
19161921

1922+
//go:nocheckptr
1923+
// This prevents inlining Value.UnsafeAddr when -d=checkptr is enabled,
1924+
// which ensures cmd/compile can recognize unsafe.Pointer(v.UnsafeAddr())
1925+
// and make an exception.
1926+
19171927
// UnsafeAddr returns a pointer to v's data.
19181928
// It is for advanced clients that also import the "unsafe" package.
19191929
// It panics if v is not addressable.

src/runtime/checkptr.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2019 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package runtime
6+
7+
import "unsafe"
8+
9+
type ptrAlign struct {
10+
ptr unsafe.Pointer
11+
align uintptr
12+
}
13+
14+
func checkptrAlignment(p unsafe.Pointer, elem *_type) {
15+
// TODO(mdempsky): What about fieldAlign?
16+
if uintptr(p)&(uintptr(elem.align)-1) != 0 {
17+
panic(ptrAlign{p, uintptr(elem.align)})
18+
}
19+
}
20+
21+
type ptrArith struct {
22+
ptr unsafe.Pointer
23+
originals []unsafe.Pointer
24+
}
25+
26+
func checkptrArithmetic(p unsafe.Pointer, originals []unsafe.Pointer) {
27+
if 0 < uintptr(p) && uintptr(p) < minLegalPointer {
28+
panic(ptrArith{p, originals})
29+
}
30+
31+
base := checkptrBase(p)
32+
if base == 0 {
33+
return
34+
}
35+
36+
for _, original := range originals {
37+
if base == checkptrBase(original) {
38+
return
39+
}
40+
}
41+
42+
panic(ptrArith{p, originals})
43+
}
44+
45+
func checkptrBase(p unsafe.Pointer) uintptr {
46+
base, _, _ := findObject(uintptr(p), 0, 0)
47+
// TODO(mdempsky): If base == 0, then check if p points to the
48+
// stack or a global variable.
49+
return base
50+
}

src/strings/builder.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type Builder struct {
2424
// USE CAREFULLY!
2525
// This was copied from the runtime; see issues 23382 and 7921.
2626
//go:nosplit
27+
//go:nocheckptr
2728
func noescape(p unsafe.Pointer) unsafe.Pointer {
2829
x := uintptr(p)
2930
return unsafe.Pointer(x ^ 0)

0 commit comments

Comments
 (0)