Skip to content

Commit 9dcc58c

Browse files
cmd/cgo, runtime: add checks for passing pointers from Go to C
This implements part of the proposal in issue 12416 by adding dynamic checks for passing pointers from Go to C. This code is intended to be on at all times. It does not try to catch every case. It does not implement checks on calling Go functions from C. The new cgo checks may be disabled using GODEBUG=cgocheck=0. Update #12416. Change-Id: I48de130e7e2e83fb99a1e176b2c856be38a4d3c8 Reviewed-on: https://go-review.googlesource.com/16003 Reviewed-by: Russ Cox <[email protected]>
1 parent 9c8cd83 commit 9dcc58c

File tree

11 files changed

+965
-45
lines changed

11 files changed

+965
-45
lines changed

misc/cgo/errors/ptr.go

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
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+
// Tests that cgo detects invalid pointer passing at runtime.
6+
7+
package main
8+
9+
import (
10+
"bufio"
11+
"bytes"
12+
"fmt"
13+
"io"
14+
"io/ioutil"
15+
"os"
16+
"os/exec"
17+
"path/filepath"
18+
"runtime"
19+
"strings"
20+
"sync"
21+
)
22+
23+
// ptrTest is the tests without the boilerplate.
24+
type ptrTest struct {
25+
c string // the cgo comment
26+
imports []string // a list of imports
27+
support string // supporting functions
28+
body string // the body of the main function
29+
fail bool // whether the test should fail
30+
}
31+
32+
var ptrTests = []ptrTest{
33+
{
34+
// Passing a pointer to a struct that contains a Go pointer.
35+
c: `typedef struct s { int *p; } s; void f(s *ps) {}`,
36+
body: `C.f(&C.s{new(C.int)})`,
37+
fail: true,
38+
},
39+
{
40+
// Passing a pointer to a struct that contains a Go pointer.
41+
c: `typedef struct s { int *p; } s; void f(s *ps) {}`,
42+
body: `p := &C.s{new(C.int)}; C.f(p)`,
43+
fail: true,
44+
},
45+
{
46+
// Passing a pointer to an int field of a Go struct
47+
// that (irrelevantly) contains a Go pointer.
48+
c: `struct s { int i; int *p; }; void f(int *p) {}`,
49+
body: `p := &C.struct_s{i: 0, p: new(C.int)}; C.f(&p.i)`,
50+
fail: false,
51+
},
52+
{
53+
// Passing a pointer to a pointer field of a Go struct.
54+
c: `struct s { int i; int *p; }; void f(int **p) {}`,
55+
body: `p := &C.struct_s{i: 0, p: new(C.int)}; C.f(&p.p)`,
56+
fail: true,
57+
},
58+
{
59+
// Passing a pointer to a pointer field of a Go
60+
// struct, where the field does not contain a Go
61+
// pointer, but another field (irrelevantly) does.
62+
c: `struct s { int *p1; int *p2; }; void f(int **p) {}`,
63+
body: `p := &C.struct_s{p1: nil, p2: new(C.int)}; C.f(&p.p1)`,
64+
fail: false,
65+
},
66+
{
67+
// Passing the address of a slice with no Go pointers.
68+
c: `void f(void **p) {}`,
69+
imports: []string{"unsafe"},
70+
body: `s := []unsafe.Pointer{nil}; C.f(&s[0])`,
71+
fail: false,
72+
},
73+
{
74+
// Passing the address of a slice with a Go pointer.
75+
c: `void f(void **p) {}`,
76+
imports: []string{"unsafe"},
77+
body: `i := 0; s := []unsafe.Pointer{unsafe.Pointer(&i)}; C.f(&s[0])`,
78+
fail: true,
79+
},
80+
{
81+
// Passing the address of a slice with a Go pointer,
82+
// where we are passing the address of an element that
83+
// is not a Go pointer.
84+
c: `void f(void **p) {}`,
85+
imports: []string{"unsafe"},
86+
body: `i := 0; s := []unsafe.Pointer{nil, unsafe.Pointer(&i)}; C.f(&s[0])`,
87+
fail: true,
88+
},
89+
{
90+
// Passing the address of a slice that is an element
91+
// in a struct only looks at the slice.
92+
c: `void f(void **p) {}`,
93+
imports: []string{"unsafe"},
94+
support: `type S struct { p *int; s []unsafe.Pointer }`,
95+
body: `i := 0; p := &S{p:&i, s:[]unsafe.Pointer{nil}}; C.f(&p.s[0])`,
96+
fail: false,
97+
},
98+
{
99+
// Passing the address of a static variable with no
100+
// pointers doesn't matter.
101+
c: `void f(char** parg) {}`,
102+
support: `var hello = [...]C.char{'h', 'e', 'l', 'l', 'o'}`,
103+
body: `parg := [1]*C.char{&hello[0]}; C.f(&parg[0])`,
104+
fail: false,
105+
},
106+
{
107+
// Passing the address of a static variable with
108+
// pointers does matter.
109+
c: `void f(char*** parg) {}`,
110+
support: `var hello = [...]*C.char{new(C.char)}`,
111+
body: `parg := [1]**C.char{&hello[0]}; C.f(&parg[0])`,
112+
fail: true,
113+
},
114+
}
115+
116+
func main() {
117+
os.Exit(doTests())
118+
}
119+
120+
func doTests() int {
121+
dir, err := ioutil.TempDir("", "cgoerrors")
122+
if err != nil {
123+
fmt.Fprintln(os.Stderr, err)
124+
return 2
125+
}
126+
defer os.RemoveAll(dir)
127+
128+
workers := runtime.NumCPU() + 1
129+
130+
var wg sync.WaitGroup
131+
c := make(chan int)
132+
errs := make(chan int)
133+
for i := 0; i < workers; i++ {
134+
wg.Add(1)
135+
go func() {
136+
worker(dir, c, errs)
137+
wg.Done()
138+
}()
139+
}
140+
141+
for i := range ptrTests {
142+
c <- i
143+
}
144+
close(c)
145+
146+
go func() {
147+
wg.Wait()
148+
close(errs)
149+
}()
150+
151+
tot := 0
152+
for e := range errs {
153+
tot += e
154+
}
155+
return tot
156+
}
157+
158+
func worker(dir string, c, errs chan int) {
159+
e := 0
160+
for i := range c {
161+
if !doOne(dir, i) {
162+
e++
163+
}
164+
}
165+
if e > 0 {
166+
errs <- e
167+
}
168+
}
169+
170+
func doOne(dir string, i int) bool {
171+
t := &ptrTests[i]
172+
173+
name := filepath.Join(dir, fmt.Sprintf("t%d.go", i))
174+
f, err := os.Create(name)
175+
if err != nil {
176+
fmt.Fprintln(os.Stderr, err)
177+
return false
178+
}
179+
180+
b := bufio.NewWriter(f)
181+
fmt.Fprintln(b, `package main`)
182+
fmt.Fprintln(b)
183+
fmt.Fprintln(b, `/*`)
184+
fmt.Fprintln(b, t.c)
185+
fmt.Fprintln(b, `*/`)
186+
fmt.Fprintln(b, `import "C"`)
187+
fmt.Fprintln(b)
188+
for _, imp := range t.imports {
189+
fmt.Fprintln(b, `import "`+imp+`"`)
190+
}
191+
if len(t.imports) > 0 {
192+
fmt.Fprintln(b)
193+
}
194+
if len(t.support) > 0 {
195+
fmt.Fprintln(b, t.support)
196+
fmt.Fprintln(b)
197+
}
198+
fmt.Fprintln(b, `func main() {`)
199+
fmt.Fprintln(b, t.body)
200+
fmt.Fprintln(b, `}`)
201+
202+
if err := b.Flush(); err != nil {
203+
fmt.Fprintf(os.Stderr, "flushing %s: %v\n", name, err)
204+
return false
205+
}
206+
if err := f.Close(); err != nil {
207+
fmt.Fprintln(os.Stderr, "closing %s: %v\n", name, err)
208+
return false
209+
}
210+
211+
cmd := exec.Command("go", "run", name)
212+
cmd.Dir = dir
213+
buf, err := cmd.CombinedOutput()
214+
215+
ok := true
216+
if t.fail {
217+
if err == nil {
218+
var errbuf bytes.Buffer
219+
fmt.Fprintf(&errbuf, "test %d did not fail as expected\n", i)
220+
reportTestOutput(&errbuf, i, buf)
221+
os.Stderr.Write(errbuf.Bytes())
222+
ok = false
223+
} else if !bytes.Contains(buf, []byte("Go pointer")) {
224+
var errbuf bytes.Buffer
225+
fmt.Fprintf(&errbuf, "test %d output does not contain expected error\n", i)
226+
reportTestOutput(&errbuf, i, buf)
227+
os.Stderr.Write(errbuf.Bytes())
228+
ok = false
229+
}
230+
} else {
231+
if err != nil {
232+
var errbuf bytes.Buffer
233+
fmt.Fprintf(&errbuf, "test %d failed unexpectedly: %v\n", i, err)
234+
reportTestOutput(&errbuf, i, buf)
235+
os.Stderr.Write(errbuf.Bytes())
236+
ok = false
237+
}
238+
}
239+
240+
if t.fail && ok {
241+
cmd = exec.Command("go", "run", name)
242+
cmd.Dir = dir
243+
env := []string{"GODEBUG=cgocheck=0"}
244+
for _, e := range os.Environ() {
245+
if !strings.HasPrefix(e, "GODEBUG=") {
246+
env = append(env, e)
247+
}
248+
}
249+
cmd.Env = env
250+
buf, err := cmd.CombinedOutput()
251+
if err != nil {
252+
var errbuf bytes.Buffer
253+
fmt.Fprintf(&errbuf, "test %d failed unexpectedly with GODEBUG=cgocheck=0: %v\n", i, err)
254+
reportTestOutput(&errbuf, i, buf)
255+
os.Stderr.Write(errbuf.Bytes())
256+
ok = false
257+
}
258+
}
259+
260+
return ok
261+
}
262+
263+
func reportTestOutput(w io.Writer, i int, buf []byte) {
264+
fmt.Fprintf(w, "=== test %d output ===\n", i)
265+
fmt.Fprintf(w, "%s", buf)
266+
fmt.Fprintf(w, "=== end of test %d output ===\n", i)
267+
}

misc/cgo/errors/test.bash

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,9 @@ check issue8442.go
3434
check issue11097a.go
3535
check issue11097b.go
3636

37+
if ! go run ptr.go; then
38+
exit 1
39+
fi
40+
3741
rm -rf errs _obj
3842
exit 0

misc/cgo/test/callback.go

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,47 @@ import (
1919
"path"
2020
"runtime"
2121
"strings"
22+
"sync"
2223
"testing"
2324
"unsafe"
2425
)
2526

27+
// Pass a func value from nestedCall to goCallback using an integer token.
28+
var callbackMutex sync.Mutex
29+
var callbackToken int
30+
var callbackFuncs = make(map[int]func())
31+
2632
// nestedCall calls into C, back into Go, and finally to f.
2733
func nestedCall(f func()) {
28-
// NOTE: Depends on representation of f.
2934
// callback(x) calls goCallback(x)
30-
C.callback(*(*unsafe.Pointer)(unsafe.Pointer(&f)))
35+
callbackMutex.Lock()
36+
callbackToken++
37+
i := callbackToken
38+
callbackFuncs[i] = f
39+
callbackMutex.Unlock()
40+
41+
// Pass the address of i because the C function was written to
42+
// take a pointer. We could pass an int if we felt like
43+
// rewriting the C code.
44+
C.callback(unsafe.Pointer(&i))
45+
46+
callbackMutex.Lock()
47+
delete(callbackFuncs, i)
48+
callbackMutex.Unlock()
3149
}
3250

3351
//export goCallback
3452
func goCallback(p unsafe.Pointer) {
35-
(*(*func())(unsafe.Pointer(&p)))()
53+
i := *(*int)(p)
54+
55+
callbackMutex.Lock()
56+
f := callbackFuncs[i]
57+
callbackMutex.Unlock()
58+
59+
if f == nil {
60+
panic("missing callback function")
61+
}
62+
f()
3663
}
3764

3865
func testCallback(t *testing.T) {

0 commit comments

Comments
 (0)