1// Copyright 2015 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// Code to check that pointer writes follow the cgo rules.
6// These functions are invoked when GOEXPERIMENT=cgocheck2 is enabled.
7
8package runtime
9
10import (
11	"internal/abi"
12	"internal/goarch"
13	"unsafe"
14)
15
16const cgoWriteBarrierFail = "unpinned Go pointer stored into non-Go memory"
17
18// cgoCheckPtrWrite is called whenever a pointer is stored into memory.
19// It throws if the program is storing an unpinned Go pointer into non-Go
20// memory.
21//
22// This is called from generated code when GOEXPERIMENT=cgocheck2 is enabled.
23//
24//go:nosplit
25//go:nowritebarrier
26func cgoCheckPtrWrite(dst *unsafe.Pointer, src unsafe.Pointer) {
27	if !mainStarted {
28		// Something early in startup hates this function.
29		// Don't start doing any actual checking until the
30		// runtime has set itself up.
31		return
32	}
33	if !cgoIsGoPointer(src) {
34		return
35	}
36	if cgoIsGoPointer(unsafe.Pointer(dst)) {
37		return
38	}
39
40	// If we are running on the system stack then dst might be an
41	// address on the stack, which is OK.
42	gp := getg()
43	if gp == gp.m.g0 || gp == gp.m.gsignal {
44		return
45	}
46
47	// Allocating memory can write to various mfixalloc structs
48	// that look like they are non-Go memory.
49	if gp.m.mallocing != 0 {
50		return
51	}
52
53	// If the object is pinned, it's safe to store it in C memory. The GC
54	// ensures it will not be moved or freed.
55	if isPinned(src) {
56		return
57	}
58
59	// It's OK if writing to memory allocated by persistentalloc.
60	// Do this check last because it is more expensive and rarely true.
61	// If it is false the expense doesn't matter since we are crashing.
62	if inPersistentAlloc(uintptr(unsafe.Pointer(dst))) {
63		return
64	}
65
66	systemstack(func() {
67		println("write of unpinned Go pointer", hex(uintptr(src)), "to non-Go memory", hex(uintptr(unsafe.Pointer(dst))))
68		throw(cgoWriteBarrierFail)
69	})
70}
71
72// cgoCheckMemmove is called when moving a block of memory.
73// It throws if the program is copying a block that contains an unpinned Go
74// pointer into non-Go memory.
75//
76// This is called from generated code when GOEXPERIMENT=cgocheck2 is enabled.
77//
78//go:nosplit
79//go:nowritebarrier
80func cgoCheckMemmove(typ *_type, dst, src unsafe.Pointer) {
81	cgoCheckMemmove2(typ, dst, src, 0, typ.Size_)
82}
83
84// cgoCheckMemmove2 is called when moving a block of memory.
85// dst and src point off bytes into the value to copy.
86// size is the number of bytes to copy.
87// It throws if the program is copying a block that contains an unpinned Go
88// pointer into non-Go memory.
89//
90//go:nosplit
91//go:nowritebarrier
92func cgoCheckMemmove2(typ *_type, dst, src unsafe.Pointer, off, size uintptr) {
93	if !typ.Pointers() {
94		return
95	}
96	if !cgoIsGoPointer(src) {
97		return
98	}
99	if cgoIsGoPointer(dst) {
100		return
101	}
102	cgoCheckTypedBlock(typ, src, off, size)
103}
104
105// cgoCheckSliceCopy is called when copying n elements of a slice.
106// src and dst are pointers to the first element of the slice.
107// typ is the element type of the slice.
108// It throws if the program is copying slice elements that contain unpinned Go
109// pointers into non-Go memory.
110//
111//go:nosplit
112//go:nowritebarrier
113func cgoCheckSliceCopy(typ *_type, dst, src unsafe.Pointer, n int) {
114	if !typ.Pointers() {
115		return
116	}
117	if !cgoIsGoPointer(src) {
118		return
119	}
120	if cgoIsGoPointer(dst) {
121		return
122	}
123	p := src
124	for i := 0; i < n; i++ {
125		cgoCheckTypedBlock(typ, p, 0, typ.Size_)
126		p = add(p, typ.Size_)
127	}
128}
129
130// cgoCheckTypedBlock checks the block of memory at src, for up to size bytes,
131// and throws if it finds an unpinned Go pointer. The type of the memory is typ,
132// and src is off bytes into that type.
133//
134//go:nosplit
135//go:nowritebarrier
136func cgoCheckTypedBlock(typ *_type, src unsafe.Pointer, off, size uintptr) {
137	// Anything past typ.PtrBytes is not a pointer.
138	if typ.PtrBytes <= off {
139		return
140	}
141	if ptrdataSize := typ.PtrBytes - off; size > ptrdataSize {
142		size = ptrdataSize
143	}
144
145	if typ.Kind_&abi.KindGCProg == 0 {
146		cgoCheckBits(src, typ.GCData, off, size)
147		return
148	}
149
150	// The type has a GC program. Try to find GC bits somewhere else.
151	for _, datap := range activeModules() {
152		if cgoInRange(src, datap.data, datap.edata) {
153			doff := uintptr(src) - datap.data
154			cgoCheckBits(add(src, -doff), datap.gcdatamask.bytedata, off+doff, size)
155			return
156		}
157		if cgoInRange(src, datap.bss, datap.ebss) {
158			boff := uintptr(src) - datap.bss
159			cgoCheckBits(add(src, -boff), datap.gcbssmask.bytedata, off+boff, size)
160			return
161		}
162	}
163
164	s := spanOfUnchecked(uintptr(src))
165	if s.state.get() == mSpanManual {
166		// There are no heap bits for value stored on the stack.
167		// For a channel receive src might be on the stack of some
168		// other goroutine, so we can't unwind the stack even if
169		// we wanted to.
170		// We can't expand the GC program without extra storage
171		// space we can't easily get.
172		// Fortunately we have the type information.
173		systemstack(func() {
174			cgoCheckUsingType(typ, src, off, size)
175		})
176		return
177	}
178
179	// src must be in the regular heap.
180	tp := s.typePointersOf(uintptr(src), size)
181	for {
182		var addr uintptr
183		if tp, addr = tp.next(uintptr(src) + size); addr == 0 {
184			break
185		}
186		v := *(*unsafe.Pointer)(unsafe.Pointer(addr))
187		if cgoIsGoPointer(v) && !isPinned(v) {
188			throw(cgoWriteBarrierFail)
189		}
190	}
191}
192
193// cgoCheckBits checks the block of memory at src, for up to size
194// bytes, and throws if it finds an unpinned Go pointer. The gcbits mark each
195// pointer value. The src pointer is off bytes into the gcbits.
196//
197//go:nosplit
198//go:nowritebarrier
199func cgoCheckBits(src unsafe.Pointer, gcbits *byte, off, size uintptr) {
200	skipMask := off / goarch.PtrSize / 8
201	skipBytes := skipMask * goarch.PtrSize * 8
202	ptrmask := addb(gcbits, skipMask)
203	src = add(src, skipBytes)
204	off -= skipBytes
205	size += off
206	var bits uint32
207	for i := uintptr(0); i < size; i += goarch.PtrSize {
208		if i&(goarch.PtrSize*8-1) == 0 {
209			bits = uint32(*ptrmask)
210			ptrmask = addb(ptrmask, 1)
211		} else {
212			bits >>= 1
213		}
214		if off > 0 {
215			off -= goarch.PtrSize
216		} else {
217			if bits&1 != 0 {
218				v := *(*unsafe.Pointer)(add(src, i))
219				if cgoIsGoPointer(v) && !isPinned(v) {
220					throw(cgoWriteBarrierFail)
221				}
222			}
223		}
224	}
225}
226
227// cgoCheckUsingType is like cgoCheckTypedBlock, but is a last ditch
228// fall back to look for pointers in src using the type information.
229// We only use this when looking at a value on the stack when the type
230// uses a GC program, because otherwise it's more efficient to use the
231// GC bits. This is called on the system stack.
232//
233//go:nowritebarrier
234//go:systemstack
235func cgoCheckUsingType(typ *_type, src unsafe.Pointer, off, size uintptr) {
236	if !typ.Pointers() {
237		return
238	}
239
240	// Anything past typ.PtrBytes is not a pointer.
241	if typ.PtrBytes <= off {
242		return
243	}
244	if ptrdataSize := typ.PtrBytes - off; size > ptrdataSize {
245		size = ptrdataSize
246	}
247
248	if typ.Kind_&abi.KindGCProg == 0 {
249		cgoCheckBits(src, typ.GCData, off, size)
250		return
251	}
252	switch typ.Kind_ & abi.KindMask {
253	default:
254		throw("can't happen")
255	case abi.Array:
256		at := (*arraytype)(unsafe.Pointer(typ))
257		for i := uintptr(0); i < at.Len; i++ {
258			if off < at.Elem.Size_ {
259				cgoCheckUsingType(at.Elem, src, off, size)
260			}
261			src = add(src, at.Elem.Size_)
262			skipped := off
263			if skipped > at.Elem.Size_ {
264				skipped = at.Elem.Size_
265			}
266			checked := at.Elem.Size_ - skipped
267			off -= skipped
268			if size <= checked {
269				return
270			}
271			size -= checked
272		}
273	case abi.Struct:
274		st := (*structtype)(unsafe.Pointer(typ))
275		for _, f := range st.Fields {
276			if off < f.Typ.Size_ {
277				cgoCheckUsingType(f.Typ, src, off, size)
278			}
279			src = add(src, f.Typ.Size_)
280			skipped := off
281			if skipped > f.Typ.Size_ {
282				skipped = f.Typ.Size_
283			}
284			checked := f.Typ.Size_ - skipped
285			off -= skipped
286			if size <= checked {
287				return
288			}
289			size -= checked
290		}
291	}
292}
293