Skip to content

Commit 7aeaad5

Browse files
runtime/cgo: when using msan explicitly unpoison cgoCallers
This avoids an incorrect msan uninitialized memory report when using runtime.SetCgoTraceback when a signal occurs while the fifth argument register is undefined. See the issue for more details. Fixes #47543 Change-Id: I3d1b673e2c93471ccdae0171a99b88b5a6062840 Reviewed-on: https://go-review.googlesource.com/c/go/+/339902 Trust: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Austin Clements <[email protected]>
1 parent 507cc34 commit 7aeaad5

File tree

3 files changed

+130
-0
lines changed

3 files changed

+130
-0
lines changed

misc/cgo/testsanitizers/msan_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func TestMSAN(t *testing.T) {
4242
{src: "msan5.go"},
4343
{src: "msan6.go"},
4444
{src: "msan7.go"},
45+
{src: "msan8.go"},
4546
{src: "msan_fail.go", wantErr: true},
4647
}
4748
for _, tc := range cases {
+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright 2021 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 main
6+
7+
/*
8+
#include <pthread.h>
9+
#include <signal.h>
10+
#include <stdint.h>
11+
12+
#include <sanitizer/msan_interface.h>
13+
14+
// cgoTracebackArg is the type of the argument passed to msanGoTraceback.
15+
struct cgoTracebackArg {
16+
uintptr_t context;
17+
uintptr_t sigContext;
18+
uintptr_t* buf;
19+
uintptr_t max;
20+
};
21+
22+
// msanGoTraceback is registered as the cgo traceback function.
23+
// This will be called when a signal occurs.
24+
void msanGoTraceback(void* parg) {
25+
struct cgoTracebackArg* arg = (struct cgoTracebackArg*)(parg);
26+
arg->buf[0] = 0;
27+
}
28+
29+
// msanGoWait will be called with all registers undefined as far as
30+
// msan is concerned. It just waits for a signal.
31+
// Because the registers are msan-undefined, the signal handler will
32+
// be invoked with all registers msan-undefined.
33+
__attribute__((noinline))
34+
void msanGoWait(unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5, unsigned long a6) {
35+
sigset_t mask;
36+
37+
sigemptyset(&mask);
38+
sigsuspend(&mask);
39+
}
40+
41+
// msanGoSignalThread is the thread ID of the msanGoLoop thread.
42+
static pthread_t msanGoSignalThread;
43+
44+
// msanGoSignalThreadSet is used to record that msanGoSignalThread
45+
// has been initialized. This is accessed atomically.
46+
static int32_t msanGoSignalThreadSet;
47+
48+
// uninit is explicitly poisoned, so that we can make all registers
49+
// undefined by calling msanGoWait.
50+
static unsigned long uninit;
51+
52+
// msanGoLoop loops calling msanGoWait, with the arguments passed
53+
// such that msan thinks that they are undefined. msan permits
54+
// undefined values to be used as long as they are not used to
55+
// for conditionals or for memory access.
56+
void msanGoLoop() {
57+
int i;
58+
59+
msanGoSignalThread = pthread_self();
60+
__atomic_store_n(&msanGoSignalThreadSet, 1, __ATOMIC_SEQ_CST);
61+
62+
// Force uninit to be undefined for msan.
63+
__msan_poison(&uninit, sizeof uninit);
64+
for (i = 0; i < 100; i++) {
65+
msanGoWait(uninit, uninit, uninit, uninit, uninit, uninit);
66+
}
67+
}
68+
69+
// msanGoReady returns whether msanGoSignalThread is set.
70+
int msanGoReady() {
71+
return __atomic_load_n(&msanGoSignalThreadSet, __ATOMIC_SEQ_CST) != 0;
72+
}
73+
74+
// msanGoSendSignal sends a signal to the msanGoLoop thread.
75+
void msanGoSendSignal() {
76+
pthread_kill(msanGoSignalThread, SIGWINCH);
77+
}
78+
*/
79+
import "C"
80+
81+
import (
82+
"runtime"
83+
"time"
84+
)
85+
86+
func main() {
87+
runtime.SetCgoTraceback(0, C.msanGoTraceback, nil, nil)
88+
89+
c := make(chan bool)
90+
go func() {
91+
defer func() { c <- true }()
92+
C.msanGoLoop()
93+
}()
94+
95+
for C.msanGoReady() == 0 {
96+
time.Sleep(time.Microsecond)
97+
}
98+
99+
loop:
100+
for {
101+
select {
102+
case <-c:
103+
break loop
104+
default:
105+
C.msanGoSendSignal()
106+
time.Sleep(time.Microsecond)
107+
}
108+
}
109+
}

src/runtime/cgo/gcc_traceback.c

+20
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
#include <stdint.h>
88
#include "libcgo.h"
99

10+
#ifndef __has_feature
11+
#define __has_feature(x) 0
12+
#endif
13+
14+
#if __has_feature(memory_sanitizer)
15+
#include <sanitizer/msan_interface.h>
16+
#endif
17+
1018
// Call the user's traceback function and then call sigtramp.
1119
// The runtime signal handler will jump to this code.
1220
// We do it this way so that the user's traceback function will be called
@@ -19,6 +27,18 @@ x_cgo_callers(uintptr_t sig, void *info, void *context, void (*cgoTraceback)(str
1927
arg.SigContext = (uintptr_t)(context);
2028
arg.Buf = cgoCallers;
2129
arg.Max = 32; // must match len(runtime.cgoCallers)
30+
31+
#if __has_feature(memory_sanitizer)
32+
// This function is called directly from the signal handler.
33+
// The arguments are passed in registers, so whether msan
34+
// considers cgoCallers to be initialized depends on whether
35+
// it considers the appropriate register to be initialized.
36+
// That can cause false reports in rare cases.
37+
// Explicitly unpoison the memory to avoid that.
38+
// See issue #47543 for more details.
39+
__msan_unpoison(&arg, sizeof arg);
40+
#endif
41+
2242
(*cgoTraceback)(&arg);
2343
sigtramp(sig, info, context);
2444
}

0 commit comments

Comments
 (0)